if (!Function.prototype.bind) {
    Function.prototype.bind = function(oThis) {
        if (typeof this !== 'function') {
            // closest thing possible to the ECMAScript 5
            // internal IsCallable function
            throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
        }

        var aArgs   = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP    = function() {},
            fBound  = function() {
                return fToBind.apply(this instanceof fNOP && oThis
                        ? this
                        : oThis,
                    aArgs.concat(Array.prototype.slice.call(arguments)));
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}

/*
 * Released under BSD License
 * Copyright (c) 2014-2015 hizzgdev@163.com
 *
 * Project Home:
 *   https://github.com/hizzgdev/jsmind/
 */
JSMIND_MODE = {}; // 题纲编辑器的工作模式:1题纲模版模式\2题纲模式\3分页模式
JSMIND_MODE.TG_TEMPLATE = 1;        // 题纲模版模式
JSMIND_MODE.TG = 2;                 // 题纲模式，能显示"设置节点属性、初始值等功能"的按钮
JSMIND_MODE.TG_TO_PAGE = 3;         // 分页模式，能显示"设置节点的分页"的按钮


JSMIND_CONST = {};
JSMIND_CONST.LEVEL_LIMIT = 5; // 支持的最大层数

JSMIND_MSG = {};
JSMIND_MSG.MSG_3_LEVEL_LIMIT = "目前最多支持"+JSMIND_CONST.LEVEL_LIMIT+"级结构！";
JSMIND_MSG.MSG_CAN_NOT_REMOVE = "不能继续删除！";
JSMIND_MSG.MSG_SELECT_NODE_PROMOT = "选中节点后，可以添加子节点、兄弟节点、文字备注";
JSMIND_MSG.MSG_CAN_NOT_DROP_ON_SELF = "不能添加到自身";
JSMIND_MSG.MSG_CAN_NOT_SET_PAGE_OF_GOURP_KIND_CHILD = "请到数组节点上设置页码";
JSMIND_MSG.MSG_CAN_SET_GROUP_KIND_FROM_STRUCT = '只能将“结构体节点”设置为“数组节点”';
JSMIND_MSG.MSG_CAN_SET_GROUP_KIND_FROM_CHILDREN = '只能将“含有子和孙子的节点”设置为“数组节点”';
JSMIND_MSG.MSG_CAN_SET_GROUP_KIND_FROM_SAME_CHILDREN = '只能将“含有相同结构的子和孙子的节点”设置为“数组节点”';
JSMIND_MSG.MSG_CAN_SET_ONE_PAGE_TO_GROUP_KIND = "只能为数组制定一个页";
JSMIND_MSG.MSG_CAN_NOT_SET_PRESERVE_OR_OPTIONAL_GOURP_KIND_CHILD = "不能将保留或可选加入“数组节点”";

//preprocess node
function preprocess(node,text_limit){
	// value存在时不用重新赋值
	if(!!node.value) return;
		
	// type和value无效的数据设置初始值
	// (0无/1文字/2图片/3图表/4表格/5图形)
	if(node.type==0){
		node.type = 1;
		preprocess(node,null);
		
	}else if(node.type==1){
		var v = new SJGReplaceItemText();
		v.value=node.topic;
		v.min=1;
		if(text_limit){
			v.max = text_limit;
		}else{
			v.max = v.value.length;
		}
		node.value=v;
		
	}else if(node.type==2){
        var img=new SJGReplaceItemImage();
        img.value="http://itbour-user.oss-cn-hangzhou.aliyuncs.com/image/U9/2017/02/07/183001189_E3uV1gIq6O9ZTOMHDzq3.jpg";
        img.pos.w=100;
        img.pos.h=100;
        node.value=img;
        
	}else if(node.type==3){
		// TODO
		
	}else if(node.type==4){
		var tbl=new SJGReplaceItemTable();
		tbl.minRow=1;
		tbl.maxRow=4;
		tbl.minCol=1;
		tbl.maxCol=4;
		node.value=tbl;
		
	}else if(node.type==5){// added 2017-09-01
		var gph=new SJGReplaceItemGraph();
		gph.gt=0;
		var defData = {"paths":[{"points":[[25.46,0.12,8],[25.84,0.23,3],[26.17,0.49,3],[26.37,0.86,3],[30.85,9.22,1],[34.41,7.38,3],[38.46,6.41,3],[42.74,6.41,3],[46.72,6.41,3],[50.5,7.25,3],[53.87,8.83,3],[57.63,2.07,1],[57.84,1.7,3],[58.18,1.45,3],[58.56,1.34,3],[58.94,1.23,3],[59.36,1.26,3],[59.74,1.46,3],[60.49,1.87,3],[60.76,2.81,3],[60.35,3.55,3],[56.63,10.23,1],[64.14,14.56,3],[69.3,22.38,3],[69.89,31.44,3],[15.59,31.44,1],[16.16,22.68,3],[21.01,15.08,3],[28.09,10.62,3],[23.63,2.3,1],[23.23,1.55,3],[23.52,0.62,3],[24.28,0.23,3],[24.66,0.03,3],[25.08,0,3],[25.46,0.12,3],[25.46,0.12,9]],"w":0,"h":0},{"points":[[31.07,18.93,8],[29.56,18.93,3],[28.33,20.14,3],[28.33,21.64,3],[28.33,23.13,3],[29.56,24.34,3],[31.07,24.34,3],[32.58,24.34,3],[33.81,23.13,3],[33.81,21.64,3],[33.81,20.14,3],[32.58,18.93,3],[31.07,18.93,3],[31.07,18.93,9]],"w":0,"h":0},{"points":[[53,18.93,8],[51.49,18.93,3],[50.26,20.14,3],[50.26,21.64,3],[50.26,23.13,3],[51.49,24.34,3],[53,24.34,3],[54.51,24.34,3],[55.74,23.13,3],[55.74,21.64,3],[55.74,20.14,3],[54.51,18.93,3],[53,18.93,3],[53,18.93,9]],"w":0,"h":0},{"points":[[6,33.92,8],[9.31,33.92,3],[11.99,36.58,3],[11.99,39.85,3],[11.99,64.39,1],[11.99,67.67,3],[9.31,70.32,3],[6,70.32,3],[2.68,70.32,3],[0,67.67,3],[0,64.39,3],[0,39.85,1],[0,36.58,3],[2.68,33.92,3],[6,33.92,3],[6,33.92,9]],"w":0,"h":0},{"points":[[77.85,33.92,8],[81.16,33.92,3],[83.85,36.58,3],[83.85,39.85,3],[83.85,64.39,1],[83.85,67.67,3],[81.16,70.32,3],[77.85,70.32,3],[74.54,70.32,3],[71.85,67.67,3],[71.85,64.39,3],[71.85,39.85,1],[71.85,36.58,3],[74.54,33.92,3],[77.85,33.92,3],[77.85,33.92,9]],"w":0,"h":0},{"points":[[15.38,34.3,8],[69.06,34.3,1],[68.58,72.28,1],[68.58,76.41,3],[65.19,79.76,3],[61.02,79.76,3],[58.04,79.76,1],[58.04,94.07,1],[58.04,97.34,3],[55.35,100,3],[52.04,100,3],[48.73,100,3],[46.04,97.34,3],[46.04,94.07,3],[46.04,79.76,1],[38.36,79.76,1],[38.36,94.07,1],[38.36,97.34,3],[35.67,100,3],[32.36,100,3],[29.05,100,3],[26.36,97.34,3],[26.36,94.07,3],[26.36,79.76,1],[22.95,79.76,1],[18.77,79.76,3],[15.38,76.41,3],[15.38,72.28,3],[15.38,57.31,1],[15.38,42.35,1],[15.38,34.3,1],[15.38,34.3,9]],"w":0,"h":0}],"w":83.85,"h":100,"x":0,"y":0};
		gph.cp=new CompoundPath();
		gph.cp.fromJson(defData,0);
		node.value=gph;
	}
}


(function($w){
    'use strict';
    // set 'jsMind' as the library name.
    // __name__ should be a const value, Never try to change it easily.
    var __name__ = 'jsMind';
    // library version
    var __version__ = '0.3';
    // author
    var __author__ = 'hizzgdev@163.com';

    // an noop function define
    var _noop = function(){};
    //var logger = (typeof console === 'undefined')?{
    //        log:_noop, debug:_noop, error:_noop, warn:_noop, info:_noop
    //    }:console;
    var logger = {
        log:_noop,
        debug:_noop,
        error:_noop,
        warn:_noop,
        info:_noop};

    // check global variables
    if(typeof $w[__name__] != 'undefined'){
        logger.log(__name__+' has been already exist.');
        return;
    }

    // shortcut of methods in dom
    var $d = $w.document;
    var $g = function(id){return $d.getElementById(id);};
    var $c = function(tag){return $d.createElement(tag);};
    var $t = function(n,t){if(n.hasChildNodes()){n.firstChild.nodeValue = t;}else{n.appendChild($d.createTextNode(t));}};
    var $h = function(n,t){n.innerHTML = t;};

    var DEFAULT_OPTIONS = {
        container : '',   // id of the container
        editable : false, // you can change it in your options
        theme : null,
        mode :'full',     // full or side
        support_html : true,

        view:{
            hmargin:100,
            vmargin:50,
            line_width:1,
            line_color:'#AAA'
        },
        layout:{
            hspace:30,
            vspace:20,
            pspace:15
        },
        shortcut:{
            enable:true,
            handles:{
            },
            mapping:{
                addchild   : 45, // Insert
                addbrother : 13, // Enter
                editnode   : 113,// F2
                delnode    : 46, // Delete
                toggle     : 32, // Space
                left       : 37, // Left
                up         : 38, // Up
                right      : 39, // Right
                down       : 40 // Down
            }
        }
    };

    // core object
    var jm = function(options){
        jm.current = this;

        this.edit_mode = options.edit_mode||JSMIND_MODE.TG_TEMPLATE ;


        this.version = __version__;
        var opts = {};
        jm.util.json.merge(opts, DEFAULT_OPTIONS);
        jm.util.json.merge(opts, options);

        if(opts.container == null || opts.container.length == 0){
            logger.error('the options.container should not be empty.');
            return;
        }
        this.options = opts;
        this.options.editable =this.edit_mode==JSMIND_MODE.TG_TEMPLATE?true:false;// 只有在“题纲模版”模式下，才能编辑
        this.inited = false;
        this.mind = null;
        this.event_handles = [];
        this.init();
    };

    // ============= static object =============================================
    jm.direction = {left:-1,center:0,right:1};
    jm.event_type = {show:1,resize:2,edit:3,select:4};

    jm.node = function(sId,iIndex,sTopic,oData,bIsRoot,oParent,eDirection,bExpanded,image,comment,
        type,kind, page_index, value, obj_id,h,o,p,relations){
        if(!sId){logger.error('invalid nodeid');return;}
        if(typeof iIndex != 'number'){logger.error('invalid node index');return;}
        if(typeof bExpanded === 'undefined'){bExpanded = true;}
        this.id = sId;
        this.index = iIndex;
        this.topic = sTopic;
        this.data = oData;
        this.isroot = bIsRoot;
        this.parent = oParent;
        this.direction = eDirection;
        this.expanded = !!bExpanded;
        this.children = [];
        this._data = {};
        this.image = image||null;   // 废弃
        this.comment = comment||''; // 废弃

        this.type   =0||type;
        this.kind   =0||kind;
        
        this.value  =null||value;
        
        // 版本1、版本2中使用的字段,在新版本中废弃
        this.obj_id =null||obj_id;
        this.page_index =null||page_index;
        
        // 从版本3开始使用的字段：
        this.h=0; // 1表示隐藏hidden ，否则表示显示，此属性为1时，相应节点的特征值为-1[在题纲编辑器和design编辑器保存时生成]
        if(typeof h!= "undefined"){this.h = h;}
        
        this.o=0; // 1表示可选项optional，否则表示必须项[在题纲编辑器中设置]
        if(typeof o!= "undefined"){this.o = o;}
        
        this.p=0; // 1表示保留的节点preserve，否则表示使用中，此属性主要为预防后续增加节点时使用[在题纲编辑器中设置]
        if(typeof p!= "undefined"){this.p = p;}
        
        this.relations=[];// 节点和版面对象的对应关系,［在分页映射时设置］
        if(typeof relations!= "undefined"){this.relations = relations;}
        
        
    };

    jm.node.compare=function(node1,node2){
        // '-1' is alwary the last
        var r = 0;
        var i1 = node1.index;
        var i2 = node2.index;
        if(i1>=0 && i2>=0){
            r = i1-i2;
        }else if(i1==-1 && i2==-1){
            r = 0;
        }else if(i1==-1){
            r = 1;
        }else if(i2==-1){
            r = -1;
        }else{
            r = 0;
        }
        //logger.debug(i1+' <> '+i2+'  =  '+r);
        return r;
    };

    jm.node.inherited=function(pnode,node){
        if(!!pnode && !!node){
            if(pnode.id === node.id){
                return true;
            }
            if(pnode.isroot){
                return true;
            }
            var pid = pnode.id;
            var p = node;
            while(!p.isroot){
                p = p.parent;
                if(p.id === pid){
                    return true;
                }
            }
        }
        return false;
    };

    jm.node.prototype = {
        get_location:function(){
            var vd = this._data.view;
            return {
                x:vd.abs_x,
                y:vd.abs_y
            };
        },
        get_size:function(){
            var vd = this._data.view;
            return {
                w:vd.width,
                h:vd.height
            }
        },
        is_unsupport_level:function(){
            return this.get_level()>JSMIND_CONST.LEVEL_LIMIT;
        },
        is_leaf_level:function(){
            return this.get_level()==JSMIND_CONST.LEVEL_LIMIT;
        },
        is_reach_unsupport_level:function(dest_node){
            if(!dest_node)  return false;
            return dest_node.get_level() + this.get_level_down()>JSMIND_CONST.LEVEL_LIMIT;
        },
        // 取得从root到自己的层次，从1开始
        get_level:function(){
            var l = 1;
            var p = this.parent;
            while(p){
                l++;
                p = p.parent;
            }
            return l;
        },
        // 取得从自己最底层子的层次，从1开始
        get_level_down:function(){
            var l = 1;
            var max = 0;
            if(this.children){
                for(var i=0; i<this.children.length; i++){
                    var child = this.children[i];
                    if(child){
                        var l_d = child.get_level_down();
                        max = max>l_d?max:l_d;
                    }
                }
            }
            l += max;
            return l;
        },
        // 当前节点是否符合数组节点的要求
        is_satisfy_group_kind:function(){
        	if(this.kind!=2)	{
        		alert(JSMIND_MSG.MSG_CAN_SET_GROUP_KIND_FROM_STRUCT);
        		return false;
        	}
        	var len = this.children.length; 
        	if(len==0){
        		alert(JSMIND_MSG.MSG_CAN_SET_GROUP_KIND_FROM_CHILDREN);
        		return false;
        	}
        	var _glen = this.children[0].children.length; 
        	for(var i=0;i<len;i++){
        		var child = this.children[i];
        		var glen = child.children.length; 
        		if(glen==0)
        		{
            		alert(JSMIND_MSG.MSG_CAN_SET_GROUP_KIND_FROM_CHILDREN);
            		return false;
            	}
        		if(_glen!=glen){
        			alert(JSMIND_MSG.MSG_CAN_SET_GROUP_KIND_FROM_SAME_CHILDREN);
            		return false;
        		}
        		for(var j=0;j<glen;j++){
        			var gchild = child.children[j];
        			if(gchild.children.length!=0){
        				alert(JSMIND_MSG.MSG_CAN_SET_GROUP_KIND_FROM_CHILDREN);
                		return false;
        			}
        		}
        	}
        	
        	return true;
        },
        // 当前节点是否为 组节点  的子
        is_child_node_of_group_kind:function(){
            var p = this.parent;
            while(p){
                if(p.kind == 1){
                    return true;
                }
                p = p.parent;
            }
            return false;
        },
        // 当前节点是否为  组节点
        is_node_of_group_kind:function(){
            return this.kind==1;
        },
        // 将当前节点的page_index设置到子上
        set_page_index_to_child:function(){
        	
            var pi = this.page_index;
            for(var i=0; i<this.children.length; i++){
                var child = this.children[i];
                if(child){
                    child.page_index = pi;
                    child.set_page_index_to_child();
                }
            }        		
        	

        },
        // 将当前节点的page_index设置到子上
        set_page_index_to_child_v3:function(){
        	
        		var pi=this.relations[0].page_index;
                var pci=this.relations[0].page_content_index;
                for(var i=0;i<this.children.length;i++) {
                    var child=(this.children[i]);
                    if(child) {
                    	if(child.relations.length==0){
                    		child.relations.push({
                    			page_index:pi,
                    			page_content_index:pci
                    		});
                    	}else{
                    		child.relations[0].page_index=pi;
                            child.relations[0].page_content_index=pci;
                    	}   
                    	child.set_page_index_to_child_v3();
                    }
                }
        	
        },
        // 获取当前节点的显示内容
        get_node_tips:function(){
            var tips = [];

            var page_index_str = "";
            if(this.hasOwnProperty('relations')&& this.relations.length>0){
            	for(var x=0;x<this.relations.length;x++){
            		if(x>0)
            			page_index_str+=',';
            		page_index_str += ((this.relations[x].page_index*1+1)+'');
            	}
            }else{
            	if(this.page_index!=null)
            		page_index_str = (this.page_index+1)+'';
            }

            var t1 =(page_index_str.length>0)? (""+page_index_str+"页"):("?页");
            if(t1) tips.push(t1) ;
            //自身加子的整体的类型(0根节点/1数组/2结构体/3叶节点)
            var t2  = this.kind==0?"根节点":(
                this.kind==1?"数组":(
                    this.kind==2?"结构体":(
                        this.kind==3?"叶节点":null
                    )
                )
            );
            if(t2) tips.push(t2);
            // 自身（不包含子）的表现形式1(0无/1文字/2图片/3图表/4表格)
            var t3  = this.type==0?"无":(
                this.type==1?"文字":(
                    this.type==2?"图片":(
                		this.type==3?"图表":(
            				this.type==4?"表格":null
						)
                    )
                )
            );
            if(t3) tips.push(t3);
//            // test
//            if(this.type==1){
//            	tips.push(this.topic);
//            }


            return tips.join('-');
        },
        // 刷新tips
        refresh_tips_of_view:function(){
            $t(this._data.view.node_tips,this.get_node_tips());
            for(var i=0; i<this.children.length; i++){
                var child = this.children[i];
                if(child){
                    child.refresh_tips_of_view();
                }
            }
        }


};


    jm.mind = function(){
        this.name = null;
        this.author = null;
        this.version = null;
        this.root = null;
        this.selected = null;
        this.nodes = {};
    };

    jm.mind.prototype = {
        get_node:function(nodeid){
            if(nodeid in this.nodes){
                return this.nodes[nodeid];
            }else{
                logger.warn('the node[id='+nodeid+'] can not be found');
                return null;
            }
        },
        get_node_size:function(){
            var count = 0;
            for(var nodeid in this.nodes){
                var node = this.nodes[nodeid];
                if(node){
                    count++;
                }
            }
            return count;
        },
        set_root:function(nodeid, topic, data,type,kind, page_index, value, obj_id){
            if(this.root == null){
                this.root = new jm.node(nodeid, 0, topic, data, true,null,1,null,null,null,
                    type,kind, page_index, value, obj_id);
                this._put_node(this.root);
            }else{
                logger.error('root node is already exist');
            }
        },

        add_node:function(parent_node, nodeid, topic, data, idx, direction, expanded, image, comment,
                          type,kind, page_index, value, obj_id,h,o,p,relations){
            if(typeof parent_node === 'string' || typeof parent_node === 'number'){
                return this.add_node(this.get_node(parent_node), nodeid, topic, data, idx, direction, expanded, image, comment,
                    type,kind, page_index, value, obj_id,h,o,p,relations);
            }
            var nodeindex = idx || -1;
            if(!!parent_node){
                //logger.debug(parent_node);
                var node = null;
                if(parent_node.isroot){
                    var d = jm.direction.right;
                    //if(isNaN(direction)){
                    //    var children = parent_node.children;
                    //    var children_len = children.length;
                    //    var r = 0;
                    //    for(var i=0;i<children_len;i++){if(children[i].direction === jm.direction.left){r--;}else{r++;}}
                    //    d = (children_len > 1 && r > 0) ? jm.direction.left : jm.direction.right
                    //}else{
                    //    d = (direction != jm.direction.left) ? jm.direction.right : jm.direction.left;
                    //}
                    node = new jm.node(nodeid,nodeindex,topic,data,false,parent_node,d,expanded, image, comment,
                        type,kind, page_index, value, obj_id,h,o,p,relations);
                }else{
                    node = new jm.node(nodeid,nodeindex,topic,data,false,parent_node,parent_node.direction,expanded, image, comment,
                        type,kind, page_index, value, obj_id,h,o,p,relations);
                }
                if(this._put_node(node)){
                    parent_node.children.push(node);
                    this._reindex(parent_node);
                }else{
                    logger.error('fail, the nodeid \''+node.id+'\' has been already exist.');
                    node = null;
                }
                return node;
            }else{
                logger.error('fail, the [node_parent] can not be found.');
                return null;
            }
        },

        insert_node_before:function(node_before, nodeid, topic, data){
            if(typeof node_before === 'string' || typeof node_before === 'number'){
                return this.insert_node_before(this.get_node(node_before), nodeid, topic, data);
            }
            if(!!node_before){
                var node_index = node_before.index-0.5;
                return this.add_node(node_before.parent, nodeid, topic, data, node_index);
            }else{
                logger.error('fail, the [node_before] can not be found.');
                return null;
            }
        },

        get_node_before:function(node){
            if(!node){return null;}
            if(typeof node === 'string' || typeof node === 'number'){
                return this.get_node_before(this.get_node(node));
            }
            if(node.isroot){return null;}
            var idx = node.index - 2;
            if(idx >= 0){
                return node.parent.children[idx];
            }else{
                return null;
            }
        },

        insert_node_after:function(node_after, nodeid, topic, data){
            if(typeof node_after === 'string' || typeof node_after === 'number'){
                return this.insert_node_after(this.get_node(node_after), nodeid, topic, data);
            }
            if(!!node_after){
                var node_index = node_after.index + 0.5;
                return this.add_node(node_after.parent, nodeid, topic, data, node_index);
            }else{
                logger.error('fail, the [node_after] can not be found.');
                return null;
            }
        },

        get_node_after:function(node){
            if(!node){return null;}
            if(typeof node === 'string' || typeof node === 'number'){
                return this.get_node_after(this.get_node(node));
            }
            if(node.isroot){return null;}
            var idx = node.index;
            var brothers = node.parent.children;
            if(brothers.length >= idx){
                return node.parent.children[idx];
            }else{
                return null;
            }
        },

        move_node:function(node, beforeid, parentid, direction){
            if(typeof node === 'string' || typeof node === 'number'){
                return this.move_node(this.get_node(node), beforeid, parentid, direction);
            }
            if(!parentid){
                parentid = node.parent.id;
            }
            return this._move_node(node, beforeid, parentid, direction);
        },

        _flow_node_direction:function(node,direction){
            if(typeof direction === 'undefined'){
                direction = node.direction;
            }else{
                node.direction = direction;
            }
            var len = node.children.length;
            while(len--){
                this._flow_node_direction(node.children[len],direction);
            }
        },

        _move_node_internal:function(node, beforeid){
            if(!!node && !!beforeid){
                if(beforeid == '_last_'){
                    node.index = -1;
                    this._reindex(node.parent);
                }else if(beforeid == '_first_'){
                    node.index = 0;
                    this._reindex(node.parent);
                }else{
                    var node_before = (!!beforeid)?this.get_node(beforeid):null;
                    if(node_before!=null && node_before.parent!=null && node_before.parent.id==node.parent.id){
                        node.index = node_before.index - 0.5;
                        this._reindex(node.parent);
                    }
                }
            }
            return node;
        },

        _move_node:function(node, beforeid, parentid, direction){
            if(!!node && !!parentid){
                if(node.parent.id != parentid){
                    // remove from parent's children
                    var sibling = node.parent.children;
                    var si = sibling.length;
                    while(si--){
                        if(sibling[si].id == node.id){
                            sibling.splice(si,1);
                            break;
                        }
                    }
                    node.parent = this.get_node(parentid);
                    node.parent.children.push(node);
                }

                if(node.parent.isroot){
                    if(direction == jsMind.direction.left){
                        node.direction = direction;
                    }else{
                        node.direction = jm.direction.right;
                    }
                }else{
                    node.direction = node.parent.direction;
                }
                this._move_node_internal(node, beforeid);
                this._flow_node_direction(node);
            }
            return node;
        },

        remove_node:function(node){
            if(typeof node === 'string' || typeof node === 'number'){
                return this.remove_node(this.get_node(node));
            }
            if(!node){
                logger.error('fail, the node can not be found');
                return false;
            }
            if(node.isroot){
                logger.error('fail, can not remove root node');
                return false;
            }
            if(this.selected!=null && this.selected.id == node.id){
                this.selected = null;
            }
            // clean all subordinate nodes
            var children = node.children;
            var ci = children.length;
            while(ci--){
                this.remove_node(children[ci]);
            }
            // clean all children
            children.length = 0;
            // remove from parent's children
            var sibling = node.parent.children;
            var si = sibling.length;
            while(si--){
                if(sibling[si].id == node.id){
                    sibling.splice(si,1);
                    break;
                }
            }
            // update index
            var sibling = node.parent.children;
            var si = sibling.length;
            while(si--){
                sibling[si].index = si+1;
            }

            // remove from global nodes
            delete this.nodes[node.id];
            // clean all properties
            for(var k in node){
                delete node[k];
            }
            // remove it's self
            node = null;
            //delete node;
            return true;
        },

        _put_node:function(node){
            if(node.id in this.nodes){
                logger.warn('the nodeid \''+node.id+'\' has been already exist.');
                return false;
            }else{
                this.nodes[node.id] = node;
                return true;
            }
        },

        _reindex:function(node){
            if(node instanceof jm.node){
                node.children.sort(jm.node.compare);
                for(var i=0;i<node.children.length;i++){
                    node.children[i].index = i+1;
                }
            }
        }
    };

    jm.format = {
        node_tree:{
            example:{
                "meta":{
                    "name":__name__,
                    "author":__author__,
                    "version":__version__
                },
                "format":"node_tree",
                "data":{"id":"root","topic":"一键生成"}
            },
            get_mind:function(source){
                var df = jm.format.node_tree;
                var mind = new jm.mind();
                mind.name = source.meta.name;
                mind.author = source.meta.author;
                mind.version = source.meta.version;
                df._parse(mind,source.data);
                return mind;
            },
            get_data:function(mind){
                var df = jm.format.node_tree;
                var json = {};
                json.meta = {
                    name : mind.name,
                    author : mind.author,
                    version : mind.version
                };
                json.format = 'node_tree';
                json.data = df._buildnode(mind.root);
                return json;
            },

            _parse:function(mind, node_root){
                var df = jm.format.node_tree;
                var data = df._extract_data(node_root);
                mind.set_root(node_root.id, node_root.topic, data,
                    node_root.type,node_root.kind, node_root.page_index, node_root.value, node_root.obj_id);
                if('children' in node_root){
                    var children = node_root.children;
                    for(var i=0;i<children.length;i++){
                        df._extract_subnode(mind, mind.root, children[i]);
                    }
                }
            },

            _extract_data:function(node_json){
                var data = {};
                for(var k in node_json){
                    if(k == 'id' || k=='topic' || k=='children' || k=='direction' || k=='expanded'){
                        continue;
                    }
                    data[k] = node_json[k];
                }
                return data;
            },

            _extract_subnode:function(mind, node_parent, node_json){
                var df = jm.format.node_tree;
                var data = df._extract_data(node_json);
                var d = null;
                if(node_parent.isroot){
                    d = node_json.direction == 'left'?jm.direction.left:jm.direction.right;
                }
                var node = mind.add_node(node_parent, node_json.id, node_json.topic, data, null, d, node_json.expanded);
                if('children' in node_json){
                    var children = node_json.children;
                    for(var i=0;i<children.length;i++){
                        df._extract_subnode(mind, node, children[i]);
                    }
                }
            },

            _buildnode:function(node){
                var df = jm.format.node_tree;
                if(!(node instanceof jm.node)){return;}
                var o = {
                    id : node.id,
                    topic : node.topic,
                    expanded : node.expanded
                };
                if(!!node.parent && node.parent.isroot){
                    o.direction = node.direction == jm.direction.left?'left':'right';
                }
                if(node.data != null){
                    var node_data = node.data;
                    for(var k in node_data){
                        o[k] = node_data[k];
                    }
                }
                var children = node.children;
                if(children.length > 0){
                    o.children = [];
                    for(var i=0;i<children.length;i++){
                        o.children.push(df._buildnode(children[i]));
                    }
                }
                return o;
            }
        },

        node_array:{
            example:{
                "meta":{
                    "name":__name__,
                    "author":__author__,
                    "version":__version__
                },
                "format":"node_array",
                "data":[
                    {"id":"root","topic":"一键生成", "isroot":true}
                ]
            },

            get_mind:function(source){
                var df = jm.format.node_array;
                var mind = new jm.mind();
                mind.name = source.meta.name;
                mind.author = source.meta.author;
                mind.version = source.meta.version;
                df._parse(mind,source.data);
                return mind;
            },

            get_data:function(mind){
                var df = jm.format.node_array;
                var json = {};
                json.meta = {
                    name : mind.name,
                    author : mind.author,
                    version : mind.version
                };
                json.format = 'node_array';
                json.data = [];
                df._array(mind,json.data);
                return json;
            },

            _parse:function(mind, node_array){
                var df = jm.format.node_array;
                var narray = node_array.slice(0);
                // reverse array for improving looping performance
                narray.reverse();
                var root_id = df._extract_root(mind, narray);
                if(!!root_id){
                    df._extract_subnode(mind, root_id, narray);
                }else{
                    logger.error('root node can not be found');
                }
            },

            _extract_root:function(mind, node_array){
                var df = jm.format.node_array;
                var i = node_array.length;
                while(i--){
                    if('isroot' in node_array[i] && node_array[i].isroot){
                        var root_json = node_array[i];
                        var data = df._extract_data(root_json);
                        mind.set_root(root_json.id,root_json.topic,data,
                            root_json.type,root_json.kind, root_json.page_index, root_json.value, root_json.obj_id);
                        node_array.splice(i,1);
                        return root_json.id;
                    }
                }
                return null;
            },

            _extract_subnode:function(mind, parentid, node_array){
                var df = jm.format.node_array;
                var i = node_array.length;
                var node_json = null;
                var data = null;
                var extract_count = 0;
                while(i--){
                    node_json = node_array[i];
                    if(node_json.parentid == parentid){
                        data = df._extract_data(node_json);
                        var d = null;
                        var node_direction = node_json.direction;
                        if(!!node_direction){
                            d = node_direction == 'left'?jm.direction.left:jm.direction.right;
                        }
                        mind.add_node(parentid, node_json.id, node_json.topic, data, null, d, node_json.expanded, node_json.image, node_json.comment,
                            node_json.type, node_json.kind, node_json.page_index, node_json.value, node_json.obj_id,
                            node_json.h,node_json.o,node_json.p,node_json.relations);
                        node_array.splice(i,1);
                        extract_count ++;
                        var sub_extract_count = df._extract_subnode(mind, node_json.id, node_array);
                        if(sub_extract_count > 0){
                            // reset loop index after extract subordinate node
                            i = node_array.length;
                            extract_count += sub_extract_count;
                        }
                    }
                }
                return extract_count;
            },

            _extract_data:function(node_json){
                var data = {};
                for(var k in node_json){
                    if(k == 'id' || k=='topic' || k=='parentid' || k=='isroot' || k=='direction' || k=='expanded'){
                        continue;
                    }
                    data[k] = node_json[k];
                }
                return data;
            },

            _array:function(mind, node_array){
                var df = jm.format.node_array;
                df._array_node(mind.root, node_array);
            },

            _array_node:function(node, node_array){
                var df = jm.format.node_array;
                if(!(node instanceof jm.node)){return;}
                var o = {
                    id : node.id,
                    topic : node.topic,
                    expanded : node.expanded
                };
                if(!!node.parent){
                    o.parentid = node.parent.id;
                }
                if(node.isroot){
                    o.isroot = true;
                }
                if(!!node.parent && node.parent.isroot){
                    o.direction = node.direction == jm.direction.left?'left':'right';
                }
                if(node.data != null){
                    var node_data = node.data;
                    for(var k in node_data){
                        o[k] = node_data[k];
                    }
                }
                node_array.push(o);
                var ci = node.children.length;
                for(var i=0;i<ci;i++){
                    df._array_node(node.children[i], node_array);
                }
            }
        },

        freemind:{
            example:{
                "meta":{
                    "name":__name__,
                    "author":__author__,
                    "version":__version__
                },
                "format":"freemind",
                "data":"<map version=\"1.0.1\"><node ID=\"root\" TEXT=\"freemind Example\"/></map>"
            },
            get_mind:function(source){
                var df = jm.format.freemind;
                var mind = new jm.mind();
                mind.name = source.meta.name;
                mind.author = source.meta.author;
                mind.version = source.meta.version;
                var xml = source.data;
                var xml_doc = df._parse_xml(xml);
                var xml_root = df._find_root(xml_doc);
                df._load_node(mind, null, xml_root);
                return mind;
            },

            get_data:function(mind){
                var df = jm.format.freemind;
                var json = {};
                json.meta = {
                    name : mind.name,
                    author : mind.author,
                    version : mind.version
                };
                json.format = 'freemind';
                var xmllines = [];
                xmllines.push('<map version=\"1.0.1\">');
                df._buildmap(mind.root, xmllines);
                xmllines.push('</map>');
                json.data = xmllines.join(' ');
                return json;
            },

            _parse_xml:function(xml){
                var xml_doc = null;
                if (window.DOMParser){
                    var parser = new DOMParser();
                    xml_doc = parser.parseFromString(xml,'text/xml');
                }else{ // Internet Explorer
                    xml_doc = new ActiveXObject('Microsoft.XMLDOM');
                    xml_doc.async = false;
                    xml_doc.loadXML(xml);
                }
                return xml_doc;
            },

            _find_root:function(xml_doc){
                var nodes = xml_doc.childNodes;
                var node = null;
                var root = null;
                var n = null;
                for(var i=0;i<nodes.length;i++){
                    n = nodes[i];
                    if(n.nodeType == 1 && n.tagName == 'map'){
                        node = n;
                        break;
                    }
                }
                if(!!node){
                    var ns = node.childNodes;
                    node = null;
                    for(var i=0;i<ns.length;i++){
                        n = ns[i];
                        if(n.nodeType == 1 && n.tagName == 'node'){
                            node = n;
                            break;
                        }
                    }
                }
                return node;
            },

            _load_node:function(mind, parent_id, xml_node){
                var df = jm.format.freemind;
                var node_id = xml_node.getAttribute('ID');
                var node_topic = xml_node.getAttribute('TEXT');
                // look for richcontent
                if(node_topic == null){
                    var topic_children = xml_node.childNodes;
                    var topic_child = null;
                    for(var i=0;i<topic_children.length;i++){
                        topic_child = topic_children[i];
                        //logger.debug(topic_child.tagName);
                        if(topic_child.nodeType == 1 && topic_child.tagName === 'richcontent'){
                            node_topic = topic_child.textContent;
                            break;
                        }
                    }
                }
                var node_data = df._load_attributes(xml_node);
                var node_expanded = ('expanded' in node_data)?(node_data.expanded == 'true') : true;
                delete node_data.expanded;

                var node_position = xml_node.getAttribute('POSITION');
                var node_direction = null;
                if(!!node_position){
                    node_direction = node_position=='left'?jm.direction.left:jm.direction.right;
                }
                //logger.debug(node_position +':'+ node_direction);
                if(!!parent_id){
                    mind.add_node(parent_id, node_id, node_topic, node_data, null, node_direction, node_expanded);
                }else{
                    mind.set_root(node_id, node_topic, node_data);
                }
                var children = xml_node.childNodes;
                var child = null;
                for(var i=0;i<children.length;i++){
                    child = children[i];
                    if(child.nodeType == 1 && child.tagName == 'node'){
                        df._load_node(mind, node_id, child);
                    }
                }
            },

            _load_attributes:function(xml_node){
                var children = xml_node.childNodes;
                var attr = null;
                var attr_data = null;
                for(var i=0;i<children.length;i++){
                    attr = children[i];
                    if(attr.nodeType == 1 && attr.tagName === 'attribute'){
                        if(attr_data == null){
                            attr_data = {};
                        }
                        attr_data[attr.getAttribute('NAME')] = attr.getAttribute('VALUE');
                    }
                }
                return attr_data;
            },

            _buildmap:function(node, xmllines){
                var df = jm.format.freemind;
                var pos = null;
                if(!!node.parent && node.parent.isroot){
                    pos = node.direction === jm.direction.left?'left':'right';
                }
                xmllines.push('<node');
                xmllines.push('ID=\"'+node.id+'\"');
                if(!!pos){
                    xmllines.push('POSITION=\"'+pos+'\"');
                }
                xmllines.push('TEXT=\"'+node.topic+'\">');

                // store expanded status as an attribute
                xmllines.push('<attribute NAME=\"expanded\" VALUE=\"'+node.expanded+'\"/>');

                // for attributes
                var node_data = node.data;
                if(node_data != null){
                    for(var k in node_data){
                        xmllines.push('<attribute NAME=\"'+k+'\" VALUE=\"'+node_data[k]+'\"/>');
                    }
                }

                // for children
                var children = node.children;
                for(var i=0;i<children.length;i++){
                    df._buildmap(children[i], xmllines);
                }

                xmllines.push('</node>');
            }
        }
    };

    // ============= utility object =============================================

    jm.util = {
        ajax:{
            _xhr:function(){
                var xhr = null;
                if(window.XMLHttpRequest){
                    xhr = new XMLHttpRequest();
                }else{
                    try{
                        xhr = new ActiveXObject('Microsoft.XMLHTTP');
                    }catch(e){}
                }
                return xhr;
            },
            _eurl:function(url){
                return encodeURIComponent(url);
            },
            request:function(url,param,method,callback,fail_callback){
                var a = jm.util.ajax;
                var p = null;
                var tmp_param = [];
                for(k in param){
                    tmp_param.push(a._eurl(k)+'='+a._eurl(param[k]));
                }
                if(tmp_param.length>0){
                    p = tmp_param.join('&');
                }
                var xhr = a._xhr();
                if(!xhr){return;}
                xhr.onreadystatechange = function(){
                    if(xhr.readyState == 4){
                        if(xhr.status == 200 || xhr.status == 0){
                            if(typeof callback === 'function'){
                                var data = eval('('+xhr.responseText+')');
                                callback(data);
                            }
                        }else{
                            if(typeof fail_callback === 'function'){
                                fail_callback(xhr);
                            }else{
                                logger.error('xhr request failed.',xhr);
                            }
                        }
                    }
                }
                method = method || 'GET';
                xhr.open(method,url,true);
                xhr.setRequestHeader('If-Modified-Since','0');
                if(method == 'POST'){
                    xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=utf-8');
                    xhr.send(p);
                }else{
                    xhr.send();
                }
            },
            get:function(url,callback){
                return jm.util.ajax.request(url,{},'GET',callback);
            },
            post:function(url,param,callback){
                return jm.util.ajax.request(url,param,'POST',callback);
            }
        },

        dom:{
            //target,eventType,handler
            add_event:function(t,e,h){
                if(!!t.addEventListener){
                    t.addEventListener(e,h,false);
                }else{
                    t.attachEvent('on'+e,h);
                }
            },
            remove_event:function(t,e,h){
                if(!!t.removeEventListener){
                    t.removeEventListener(e,h,false);
                }else{
                    t.removeEvent('on'+e,h);
                }
            }
        },

        canvas:{
            bezierto: function(ctx,x1,y1,x2,y2){
                ctx.beginPath();
                ctx.moveTo(x1,y1);
                ctx.bezierCurveTo(x1+(x2-x1)*2/3,y1,x1,y2,x2,y2);
                ctx.stroke();
            },
            lineto : function(ctx,x1,y1,x2,y2){
                ctx.beginPath();
                ctx.moveTo(x1,y1);
                ctx.lineTo(x2,y2);
                ctx.stroke();
            },
            clear:function(ctx,x,y,w,h){
                ctx.clearRect(x,y,w,h);
            },
            rect:function(ctx,x,y,w,h){
                ctx.beginPath();
                ctx.moveTo(x,y);
                ctx.lineTo(x+w,y);
                ctx.lineTo(x+w,y+h);
                ctx.lineTo(x,y+h);
                ctx.lineTo(x,y);
                ctx.closePath();
                ctx.stroke();
            },
            dashedLineTo:function (ctx, x1, y1, x2, y2, pattern) {
                // default interval distance -> 5px
                if (typeof pattern === "undefined") {
                    pattern = 5;
                }

                // calculate the delta x and delta y
                var dx = (x2 - x1);
                var dy = (y2 - y1);
                var distance = Math.floor(Math.sqrt(dx*dx + dy*dy));
                var dashlineInteveral = (pattern <= 0) ? distance : (distance/pattern);
                var deltay = (dy/distance) * pattern;
                var deltax = (dx/distance) * pattern;

                // draw dash line
                ctx.beginPath();
                for(var dl=0; dl<dashlineInteveral; dl++) {
                    if(dl%2) {
                        ctx.lineTo(x1 + dl*deltax, y1 + dl*deltay);
                    } else {
                        ctx.moveTo(x1 + dl*deltax, y1 + dl*deltay);
                    }
                }
                ctx.stroke();
            }
        },

        file:{
            read:function(file_data,fn_callback){
                var reader = new FileReader();
                reader.onload = function(){
                    if(typeof fn_callback === 'function'){
                        fn_callback(this.result, file_data.name);
                    }
                };
                reader.readAsText(file_data);
            },

            save:function(file_data, type, name) {
                var blob;
                if (typeof $w.Blob === 'function') {
                    blob = new Blob([file_data], {type: type});
                } else {
                    var BlobBuilder = $w.BlobBuilder || $w.MozBlobBuilder || $w.WebKitBlobBuilder || $w.MSBlobBuilder;
                    var bb = new BlobBuilder();
                    bb.append(file_data);
                    blob = bb.getBlob(type);
                }
                if (navigator.msSaveBlob) {
                    navigator.msSaveBlob(blob, name);
                } else {
                    var URL = $w.URL || $w.webkitURL;
                    var bloburl = URL.createObjectURL(blob);
                    var anchor = $c('a');
                    if ('download' in anchor) {
                        anchor.style.visibility = 'hidden';
                        anchor.href = bloburl;
                        anchor.download = name;
                        $d.body.appendChild(anchor);
                        var evt = $d.createEvent('MouseEvents');
                        evt.initEvent('click', true, true);
                        anchor.dispatchEvent(evt);
                        $d.body.removeChild(anchor);
                    } else {
                        location.href = bloburl;
                    }
                }
            }
        },

        json:{
            json2string:function(json){
                if(!!JSON){
                    try{
                        var json_str = JSON.stringify(json);
                        return json_str;
                    }catch(e){
                        logger.warn(e);
                        logger.warn('can not convert to string');
                        return null;
                    }
                }
            },
            string2json:function(json_str){
                if(!!JSON){
                    try{
                        var json = JSON.parse(json_str);
                        return json;
                    }catch(e){
                        logger.warn(e);
                        logger.warn('can not parse to json');
                        return null;
                    }
                }
            },
            merge:function(b,a){
                for(var o in a){
                    if(o in b){
                        if(b[o] && typeof b[o] === 'object' &&
                            Object.prototype.toString.call(b[o]).toLowerCase() == '[object object]' &&
                            !b[o].length){
                            jm.util.json.merge(b[o], a[o]);
                        }else{
                            b[o] = a[o];
                        }
                    }else{
                        b[o] = a[o];
                    }
                }
                return b;
            }
        },

        uuid:{
            newid:function(){
                return Number((new Date().getTime().toString(10)+Math.random().toString(10).substr(2)).substr(2,10)||'0');
            }
        },

        text:{
            is_empty:function(s){
                if(!s){return true;}
                return s.replace(/\s*/,'').length == 0;
            }
        }
    };

    jm.prototype={
        init : function(){
            if(this.inited){return;}
            this.inited = true;

            var opts = this.options;

            var opts_layout = {
                mode:opts.mode,
                hspace:opts.layout.hspace,
                vspace:opts.layout.vspace,
                pspace:opts.layout.pspace
            }
            var opts_view = {
                container:opts.container,
                support_html:opts.support_html,
                hmargin:opts.view.hmargin,
                vmargin:opts.view.vmargin,
                line_width:opts.view.line_width,
                line_color:opts.view.line_color
            };
            // create instance of function provider
            this.data = new jm.data_provider(this);
            this.layout = new jm.layout_provider(this, opts_layout);
            this.view = new jm.view_provider(this, opts_view);
            this.shortcut = new jm.shortcut_provider(this, opts.shortcut);

            this.data.init();
            this.layout.init();
            this.view.init();
            this.shortcut.init();

            this._event_bind();

            jm.init_plugins(this);
        },

        enable_edit:function(){
            this.options.editable = true;
        },

        disable_edit:function(){
            this.options.editable = false;
        },

        get_editable:function(){
            return this.options.editable;
        },

        set_theme:function(theme){
            var theme_old = this.options.theme;
            this.options.theme = (!!theme) ? theme : null;
            if(theme_old != this.options.theme){
                this.view.reset_theme();
            }
        },
        _event_bind:function(){
            var instance = this;
            this.view.add_event(this,'mousedown',this.mousedown_handle);
            this.view.add_event(this,'click',this.click_handle);
            this.view.add_event(this,'dblclick',this.dblclick_handle);

        },

        mousedown_handle:function(e){
            var element = e.target || event.srcElement;
            var isnode = this.view.is_node(element);
            var isremove_btn = this.view.is_remove_btn(element);
            var isaddsub_tool_btn = this.view.is_addsub_tool_btn(element);
            var isaddbrother_tool_btn = this.view.is_addbrother_tool_btn(element);
            var ismove_down_tool_btn = this.view.is_move_down_tool_btn(element);
            var isaddgroup_tool_btn = this.view.is_addgroup_tool_btn(element);
            var isaddattribute_tool_btn = this.view.is_addattribute_tool_btn(element);
//            var isaddpage_tool_btn = this.view.is_addpage_tool_btn(element);
            var isnode_tips = this.view.is_node_tips(element);
            if(isnode) {
                var nodeid = this.view.get_nodeid(element);
                this.select_node(nodeid);
            }else if(isremove_btn) {
                // do nothing
            }else if(isaddsub_tool_btn) {
                // do nothing
            }else if(isaddbrother_tool_btn){
                // do nothing
            }else if(ismove_down_tool_btn){
                // do nothing
            }else if(isaddgroup_tool_btn){
                // do nothing
            }else if(isaddattribute_tool_btn){
                // do nothing
//            }else if(isaddpage_tool_btn){
                // do nothing
            }else if(isnode_tips){
                // do nothing
            }else{
                this.select_clear();
            }
        },

        click_handle:function(e){
            var element = e.target || event.srcElement;
            var nodeid = this.view.get_nodeid(element);
            var isexpander = this.view.is_expander(element);
            if(isexpander){
                this.toggle_node(nodeid);
                return;
            }

            var isremove_btn = this.view.is_remove_btn(element);
            if(isremove_btn){
                this.remove_node(nodeid);
                return;
            }
            var isaddsub_tool_btn = this.view.is_addsub_tool_btn(element);
            if(isaddsub_tool_btn){
                this.add_sub_node(nodeid);
                return;
            }
            var isaddbrother_tool_btn = this.view.is_addbrother_tool_btn(element);
            if(isaddbrother_tool_btn){
                this.add_brother_node(nodeid);
                return;
            }
            var ismove_down_tool_btn = this.view.is_move_down_tool_btn(element);
            if(ismove_down_tool_btn){
                this.move_down(nodeid);
                return;
            }
            var isaddgroup_tool_btn = this.view.is_addgroup_tool_btn(element);
            if(isaddgroup_tool_btn){
                this.add_group(nodeid);
                return;
            }
            var isaddattribute_tool_btn = this.view.is_addattribute_tool_btn(element);
            if(isaddattribute_tool_btn){
                this.add_attribute(nodeid);
                return;
            }
//            var isaddpage_tool_btn = this.view.is_addpage_tool_btn(element);
//            if(isaddpage_tool_btn){
//                this.add_page(nodeid);
//                return;
//            }

        },

        dblclick_handle:function(e){
            if(this.get_editable()){
                var element = e.target || event.srcElement;
                var isnode = this.view.is_node(element);
                if(isnode){
                    var nodeid = this.view.get_nodeid(element);
                    this.begin_edit(nodeid);
                }
            }
        },

        begin_edit:function(node){
            if(typeof node === 'string' || typeof node === 'number'){
                return this.begin_edit(this.get_node(node));
            }
            if(this.get_editable()){
                if(!!node){
                    this.view.edit_node_begin(node);
                }else{
                    logger.error('the node can not be found');
                }
            }else{
                logger.error('fail, this mind map is not editable.');
                return;
            }
        },

        end_edit:function(){
            this.view.edit_node_end();
        },

        toggle_node:function(node){
            if(typeof node === 'string' || typeof node === 'number'){
                return this.toggle_node(this.get_node(node));
            }
            if(!!node){
                if(node.isroot){return;}
                this.layout.toggle_node(node);
                this.view.relayout();
            }else{
                logger.error('the node can not be found.');
            }
        },

        expand_node:function(node){
            if(typeof node === 'string' || typeof node === 'number'){
                return this.expand_node(this.get_node(node));
            }
            if(!!node){
                if(node.isroot){return;}
                this.layout.expand_node(node);
                this.view.relayout();
            }else{
                logger.error('the node can not be found.');
            }
        },

        collapse_node:function(node){
            if(typeof node === 'string' || typeof node === 'number'){
                return this.collapse_node(this.get_node(node));
            }
            if(!!node){
                if(node.isroot){return;}
                this.layout.collapse_node(node);
                this.view.relayout();
            }else{
                logger.error('the node can not be found.');
            }
        },

        expand_all:function(){
            this.layout.expand_all();
            this.view.relayout();
        },

        expand_to_depth:function(depth){
            this.layout.expand_to_depth(depth);
            this.view.relayout();
        },

        _reset:function(){
            this.view.reset();
            this.layout.reset();
            this.data.reset();
        },

        _show:function(mind){
            var m = mind || jm.format.node_array.example;

            this.mind = this.data.load(m);
            if(!this.mind){
                logger.error('data.load error');
                return;
            }else{
                logger.debug('data.load ok');
            }

            this.view.load();
            logger.debug('view.load ok');

            this.layout.layout();
            logger.debug('layout.layout ok');

            this.view.show(true);
            logger.debug('view.show ok');

            this.invoke_event_handle(jm.event_type.show,{data:[mind]});
        },

        show : function(mind){
            this._reset();
            this._show(mind);
        },

        get_meta: function(){
            return {
                name : this.mind.name,
                author : this.mind.author,
                version : this.mind.version
            };
        },

        get_data: function(data_format){
            var df = data_format || 'node_tree';
            return this.data.get_data(df);
        },

        get_root:function(){
            return this.mind.root;
        },

        get_node:function(nodeid){
            return this.mind.get_node(nodeid);
        },

        add_node:function(parent_node, nodeid, topic, data){
            if(this.get_editable()){
                var node = this.mind.add_node(parent_node, nodeid, topic, data);
                if(!!node){
                    this.view.add_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.expand_node(parent_node);
                    this.invoke_event_handle(jm.event_type.edit,{evt:'add_node',data:[parent_node.id,nodeid,topic,data],node:nodeid});
                }
                return node;
            }else{
                logger.error('fail, this mind map is not editable');
                return null;
            }
        },
        add_sub_node:function(){
            var selected_node = this.get_selected_node(); // as parent of new node
            if(!selected_node){   return; }

            if(selected_node.is_leaf_level()){
                this.options.callbacks.message(JSMIND_MSG.MSG_3_LEVEL_LIMIT,true);
                return;
            }


            var nodeid = jsMind.util.uuid.newid();
            var topic = '新节点';
            var node = this.add_node(selected_node, nodeid, topic);
            //自身加子的整体的类型(0根节点/1数组/2结构体/3叶节点)	
            selected_node.kind = 2; 
            node.kind=3;
            node.type=1;
            node.value=null;// value置空以便重新赋值
            preprocess(node,null);
            return node
        },
        add_brother_node:function(){
            var selected_node = this.get_selected_node();
            if(!selected_node)  return ;

            if(selected_node.isroot)    return ;

            var nodeid = jsMind.util.uuid.newid();
            var topic = '新节点';
            var node = this.add_node(selected_node.parent, nodeid, topic);
            node.kind=3;
            node.type=1;
            node.value=null;// value置空以便重新赋值
            preprocess(node,null);
        },
        move_up:function(nodeid) {
            var THIS = this;
            var selected_node = this.view.jm.get_node(nodeid)
            if (!selected_node)  return false;

            if(selected_node.isroot)    return false;
            if(selected_node.parent.children.length<2)  return false;

            if(selected_node.index==1)  return false;
            var prev = selected_node.parent.children[selected_node.index-2];
            this.move_node(selected_node.id, prev.id, selected_node.parent.id, selected_node.direction);
        },
        move_down:function(nodeid){
            var THIS = this;
            var selected_node = this.view.jm.get_node(nodeid)
            if(!selected_node)  return false;

            if(selected_node.isroot)    return false;
            if(selected_node.parent.children.length<2)  return false;

            if(selected_node.index==selected_node.parent.children.length)  return false;
            if(selected_node.index==selected_node.parent.children.length-1){
                this.move_node(selected_node.id,'_last_');
            }else{
                var prev = selected_node.parent.children[selected_node.index+1];
                this.move_node(selected_node.id, prev.id, selected_node.parent.id, selected_node.direction);
            }
        },
        add_group:function(nodeid){
            var THIS = this;
            var selected_node = this.get_selected_node();
            this.options.callbacks.add_group(selected_node, function(node, p){
            	if(p==0){
            		// p为0表示取消数组节点，
            		// 设置为结构体节点
            		node.kind = 3;
            		
            	}else{
            		// 否则表示，设置为数组节点
            		// 检测是否符合数组节点的要求，符合，则修改为数组节点
            		if(node.is_satisfy_group_kind()){
            			node.kind = 1;
            		}
            	}
            	
            	// 刷新界面
                node.refresh_tips_of_view();
            });
        },
        add_attribute:function(nodeid){
            var THIS = this;
            var selected_node = this.get_selected_node();
            this.options.callbacks.add_attribute(selected_node, function(node,text_limit){
            	preprocess(node,text_limit);
            	// 刷新界面
                node.refresh_tips_of_view();
            });
        },
        add_page:function(nodeid){
//            var THIS = this;
//            var selected_node = this.get_selected_node();
//            // 对于kind为1数组的子，只能到数组节点上设置的page_index
//            if(selected_node.is_child_node_of_group_kind()){
//                alert(JSMIND_MSG.MSG_CAN_NOT_SET_PAGE_OF_GOURP_KIND_CHILD);
//                return;
//            }
//
//            var cp = selected_node.page_index!=null?selected_node.page_index+1:0;
//            // 设置节点所在页
//            // 参数 selected_node:节点
//            // 参数 cp:页码作为，0表示无效，1表示首页
//            this.options.callbacks.add_page(selected_node,cp,
//                // 选择后的回调
//                // 参数node:节点
//                // 参数p:页码作为，0表示无效，1表示首页
//                function(node,p){
//                    if(p>0){
//                        node.page_index = p-1;
//                    }else{
//                        node.page_index = null;
//                    }
//
//                    // 对于kind为 1数组的情况，所有的子的page_index都设置成相同
//                    if(node.is_node_of_group_kind()){
//                        node.set_page_index_to_child();
//                    }
//
//                    // 刷新界面
//                    node.refresh_tips_of_view();
//            });
        },

        insert_node_before:function(node_before, nodeid, topic, data){
            if(this.get_editable()){
                var beforeid = (typeof node_before === 'string' || typeof node_before === 'number') ? node_before : node_before.id;
                var node = this.mind.insert_node_before(node_before, nodeid, topic, data);
                if(!!node){
                    this.view.add_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.invoke_event_handle(jm.event_type.edit,{evt:'insert_node_before',data:[beforeid,nodeid,topic,data],node:nodeid});
                }
                return node;
            }else{
                logger.error('fail, this mind map is not editable');
                return null;
            }
        },

        insert_node_after:function(node_after, nodeid, topic, data){
            if(this.get_editable()){
                var node = this.mind.insert_node_after(node_after, nodeid, topic, data);
                if(!!node){
                    this.view.add_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.invoke_event_handle(jm.event_type.edit,{evt:'insert_node_after',data:[node_after.id,nodeid,topic,data],node:nodeid});
                }
                return node;
            }else{
                logger.error('fail, this mind map is not editable');
                return null;
            }
        },

        remove_node:function(node){
            if(typeof node === 'string' || typeof node === 'number'){
                return this.remove_node(this.get_node(node));
            }
            if(this.get_editable()){
                if(!!node){
                    if(node.isroot){
                        logger.error('fail, can not remove root node');
                        return false;
                    }
                    if(node.parent && node.parent.isroot && node.parent.children.length==1){
                        // 如果只剩下根节点和一个子节点，不能删除
                        this.options.callbacks.message(JSMIND_MSG.MSG_CAN_NOT_REMOVE,true);// 弹出消息

                        return false;
                    }
                    var nodeid = node.id;
                    var parent = node.parent;
                    var parentid = parent.id;
                    this.view.remove_node(node);
                    this.mind.remove_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.invoke_event_handle(jm.event_type.edit,{evt:'remove_node',data:[nodeid],node:parentid});
                    
                    if(parent.children.length==0 && !parent.isroot){
                    	//自身加子的整体的类型(0根节点/1数组/2结构体/3叶节点)
                    	parent.kind = 3;// 删除完了，就更改kind为叶节点
                    }
                    
                }else{
                    logger.error('fail, node can not be found');
                    return false;
                }
            }else{
                logger.error('fail, this mind map is not editable');
                return;
            }
        },

        update_node:function(nodeid, topic){
            if(this.get_editable()){
                if(jm.util.text.is_empty(topic)){
                    logger.warn('fail, topic can not be empty');
                    return;
                }
                var node = this.get_node(nodeid);
                if(!!node){
                    if(node.topic === topic){
                        logger.info('nothing changed');
                        this.view.update_node(node);
                        return;
                    }
                    node.topic = topic;
                    this.view.update_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.invoke_event_handle(jm.event_type.edit,{evt:'update_node',data:[nodeid,topic],node:nodeid});
                }
            }else{
                logger.error('fail, this mind map is not editable');
                return;
            }
        },

        move_node:function(nodeid, beforeid, parentid, direction){
            if(this.get_editable()){
                var node = this.mind.move_node(nodeid,beforeid,parentid,direction);
                if(!!node){
                    this.view.update_node(node);
                    this.layout.layout();
                    this.view.show(false);
                    this.invoke_event_handle(jm.event_type.edit,{evt:'move_node',data:[nodeid,beforeid,parentid,direction],node:nodeid});
                }
            }else{
                logger.error('fail, this mind map is not editable');
                return;
            }
        },

        select_node:function(node){
            if(typeof node === 'string' || typeof node === 'number'){
                return this.select_node(this.get_node(node));
            }
            if(!this.layout.is_visible(node)){
                return;
            }
            
            this.mind.selected = node;
            if(!!node){
                this.view.select_node(node);
            }
            // 选中节点时的回调
            if(this.options.callbacks.select){
            	this.options.callbacks.select(node);
            }
            // 消息提示
            if(node && node.is_unsupport_level()){
                this.options.callbacks.message(JSMIND_MSG.MSG_3_LEVEL_LIMIT);
            }else{
                this.options.callbacks.message(JSMIND_MSG.MSG_SELECT_NODE_PROMOT);
            }

        },

        get_selected_node:function(){
            if(!!this.mind){
                return this.mind.selected;
            }else{
                return null;
            }
        },

        select_clear:function(){
            if(!!this.mind){
                this.mind.selected = null;
                this.view.select_clear();
            }
        },

        is_node_visible:function(node){
            return this.layout.is_visible(node);
        },

        find_node_before:function(node){
            if(typeof node === 'string' || typeof node === 'number'){
                return this.find_node_before(this.get_node(node));
            }
            if(!node || node.isroot){return null;}
            var n = null;
            if(node.parent.isroot){
                var c = node.parent.children;
                var prev = null;
                var ni = null;
                for(var i=0;i<c.length;i++){
                    ni = c[i];
                    if(node.direction === ni.direction){
                        if(node.id === ni.id){
                            n = prev;
                        }
                        prev = ni;
                    }
                }
            }else{
                n = this.mind.get_node_before(node);
            }
            return n;
        },

        find_node_after:function(node){
            if(typeof node === 'string' || typeof node === 'number'){
                return this.find_node_after(this.get_node(node));
            }
            if(!node || node.isroot){return null;}
            var n = null;
            if(node.parent.isroot){
                var c = node.parent.children;
                var getthis = false;
                var ni = null;
                for(var i=0;i<c.length;i++){
                    ni = c[i];
                    if(node.direction === ni.direction){
                        if(getthis){
                            n = ni;
                            break;
                        }
                        if(node.id === ni.id){
                            getthis = true;
                        }
                    }
                }
            }else{
                n = this.mind.get_node_after(node);
            }
            return n;
        },

        resize:function(){
            this.view.resize();
        },

        // callback(type ,data)
        add_event_listener:function(callback){
            if(typeof callback === 'function'){
                this.event_handles.push(callback);
            }
        },

        invoke_event_handle:function(type, data){
            var j = this;
            $w.setTimeout(function(){
                j._invoke_event_handle(type,data);
            },0);
        },

        _invoke_event_handle:function(type,data){
            var l = this.event_handles.length;
            for(var i=0;i<l;i++){
                this.event_handles[i](type,data);
            }
        }

    };

// ============= data provider =============================================

    jm.data_provider = function(jm){
        this.jm = jm;
    };

    jm.data_provider.prototype={
        init:function(){
            logger.debug('data.init');
        },

        reset:function(){
            logger.debug('data.reset');
        },
        // 从SJG的树状结构记载数据
        load_tree:function(tree){

        },
        load:function(mind_data){
            var df = null;
            var mind = null;
            if(typeof mind_data === 'object'){
                if(!!mind_data.format){
                    df = mind_data.format;
                }else{
                    df = 'node_tree';
                }
            }else{
                df = 'freemind';
            }

            if(df == 'node_array'){
                mind = jm.format.node_array.get_mind(mind_data);
            }else if(df == 'node_tree'){
                mind = jm.format.node_tree.get_mind(mind_data);
            }else if(df == 'freemind'){
                mind = jm.format.freemind.get_mind(mind_data);
            }else{
                logger.warn('unsupported format');
            }
            return mind;
        },

        get_data:function(data_format){
            var data = null;
            if(data_format == 'node_array'){
                data = jm.format.node_array.get_data(this.jm.mind);
            }else if(data_format == 'node_tree'){
                data = jm.format.node_tree.get_data(this.jm.mind);
            }else if(data_format == 'freemind'){
                data = jm.format.freemind.get_data(this.jm.mind);
            }else{
                logger.error('unsupported '+data_format+' format');
            }
            return data;
        }
    };

    // ============= layout provider ===========================================

    jm.layout_provider = function(jm, options){
        this.opts = options;
        this.jm = jm;
        this.isside = (this.opts.mode == 'side');
        this.bounds = null;

        this.cache_valid = false;
    };

    jm.layout_provider.prototype={
        init:function(){
            logger.debug('layout.init');
        },
        reset:function(){
            logger.debug('layout.reset');
            this.bounds = {n:0,s:0,w:0,e:0};
        },
        layout:function(){
            logger.debug('layout.layout');
            this.layout_direction();
            this.layout_offset();
        },

        layout_direction:function(){
            this._layout_direction_root();
        },

        _layout_direction_root:function(){
            var node = this.jm.mind.root;
            // logger.debug(node);
            var layout_data = null;
            if('layout' in node._data){
                layout_data = node._data.layout;
            }else{
                layout_data = {};
                node._data.layout = layout_data;
            }
            var children = node.children;
            var children_count = children.length;
            layout_data.direction = jm.direction.center;
            layout_data.side_index = 0;
            if(this.isside){
                var i = children_count;
                while(i--){
                    this._layout_direction_side(children[i], jm.direction.right, i);
                }
            }else{
                var i = children_count;
                var subnode = null;
                while(i--){
                    subnode = children[i];
                    if(subnode.direction == jm.direction.left){
                        this._layout_direction_side(subnode,jm.direction.left, i);
                    }else{
                        this._layout_direction_side(subnode,jm.direction.right, i);
                    }
                }
                /*
                 var boundary = Math.ceil(children_count/2);
                 var i = children_count;
                 while(i--){
                 if(i>=boundary){
                 this._layout_direction_side(children[i],jm.direction.left, children_count-i-1);
                 }else{
                 this._layout_direction_side(children[i],jm.direction.right, i);
                 }
                 }*/

            }
        },

        _layout_direction_side:function(node, direction, side_index){
            var layout_data = null;
            if('layout' in node._data){
                layout_data = node._data.layout;
            }else{
                layout_data = {};
                node._data.layout = layout_data;
            }
            var children = node.children;
            var children_count = children.length;

            layout_data.direction = direction;
            layout_data.side_index = side_index;
            var i = children_count;
            while(i--){
                this._layout_direction_side(children[i], direction, i);
            }
        },

        layout_offset:function(){
            var node = this.jm.mind.root;
            var layout_data = node._data.layout;
            layout_data.offset_x = 0;
            layout_data.offset_y = 0;
            layout_data.outer_height = 0;
            var children = node.children;
            var i = children.length;
            var left_nodes = [];
            var right_nodes = [];
            var subnode = null;
            while(i--){
                subnode = children[i];
                if(subnode._data.layout.direction == jm.direction.right){
                    right_nodes.unshift(subnode);
                }else{
                    left_nodes.unshift(subnode);
                }
            }
            layout_data.left_nodes = left_nodes;
            layout_data.right_nodes = right_nodes;
            layout_data.outer_height_left = this._layout_offset_subnodes(left_nodes);
            layout_data.outer_height_right = this._layout_offset_subnodes(right_nodes);
            this.bounds.e=node._data.view.width/2;
            this.bounds.w=0-this.bounds.e;
            //logger.debug(this.bounds.w);
            this.bounds.n=0;
            this.bounds.s = Math.max(layout_data.outer_height_left,layout_data.outer_height_right);
        },

        // layout both the x and y axis
        _layout_offset_subnodes:function(nodes){
            var total_height = 0;
            var nodes_count = nodes.length;
            var i = nodes_count;
            var node = null;
            var node_outer_height = 0;
            var layout_data = null;
            var base_y = 0;
            var pd = null; // parent._data
            while(i--){
                node = nodes[i];
                layout_data = node._data.layout;
                if(pd == null){
                    pd = node.parent._data;
                }

                node_outer_height = this._layout_offset_subnodes(node.children);
                if(!node.expanded){
                    node_outer_height=0;
                    this.set_visible(node.children,false);
                }
                node_outer_height = Math.max(node._data.view.height,node_outer_height);

                layout_data.outer_height = node_outer_height;
                layout_data.offset_y = base_y - node_outer_height/2;
                layout_data.offset_x = this.opts.hspace * layout_data.direction + pd.view.width * (pd.layout.direction + layout_data.direction)/2;
                if(!node.parent.isroot){
                    layout_data.offset_x += this.opts.pspace * layout_data.direction;
                }

                base_y = base_y - node_outer_height - this.opts.vspace;
                total_height += node_outer_height;
            }
            if(nodes_count>1){
                total_height += this.opts.vspace * (nodes_count-1);
            }
            i = nodes_count;
            var middle_height = total_height/2;
            while(i--){
                node = nodes[i];
                node._data.layout.offset_y += middle_height;
                //logger.debug(node._data.layout.offset_y);
            }
            return total_height;
        },

        // layout the y axis only, for collapse/expand a node
        _layout_offset_subnodes_height:function(nodes){
            return this._layout_offset_subnodes(nodes);
            var total_height = 0;
            var nodes_count = nodes.length;
            var i = nodes_count;
            var node = null;
            var node_outer_height = 0;
            var layout_data = null;
            var base_y = 0;
            var pd = null; // parent._data
            while(i--){
                node = nodes[i];
                layout_data = node._data.layout;
                if(pd == null){
                    pd = node.parent._data;
                }

                node_outer_height = this._layout_offset_subnodes_height(node.children);
                if(!node.expanded){
                    node_outer_height=0;
                }
                node_outer_height = Math.max(node._data.view.height,node_outer_height);

                layout_data.outer_height = node_outer_height;
                layout_data.offset_y = base_y - node_outer_height/2;
                base_y = base_y - node_outer_height - this.opts.vspace;
                total_height += node_outer_height;
            }
            if(nodes_count>1){
                total_height += this.opts.vspace * (nodes_count-1);
            }
            i = nodes_count;
            var middle_height = total_height/2;
            while(i--){
                node = nodes[i];
                node._data.layout.offset_y += middle_height;
                //logger.debug(node.topic);
                //logger.debug(node._data.layout.offset_y);
            }
            return total_height;
        },

        get_node_offset:function(node){
            var layout_data = node._data.layout;
            var offset_cache = null;
            if(('_offset_' in layout_data) && this.cache_valid){
                offset_cache = layout_data._offset_;
            }else{
                offset_cache = {x:-1, y:-1};
                layout_data._offset_ = offset_cache;
            }
            if(offset_cache.x == -1 || offset_cache.y == -1){
                var x = layout_data.offset_x;
                var y = layout_data.offset_y;
                if(!node.isroot){
                    var offset_p = this.get_node_offset(node.parent);
                    x += offset_p.x;
                    y += offset_p.y;
                }
                offset_cache.x = x;
                offset_cache.y = y;
            }
            return offset_cache;
        },

        get_node_point:function(node){
            var view_data = node._data.view;
            var offset_p = this.get_node_offset(node);
            //logger.debug(offset_p);
            var p = {};
            p.x = offset_p.x + view_data.width*(node._data.layout.direction-1)/2;
            p.y = offset_p.y-view_data.height/2;
            //logger.debug(p);
            return p;
        },

        get_node_point_in:function(node){
            var p = this.get_node_offset(node);
            return p;
        },

        get_node_point_out:function(node){
            var layout_data = node._data.layout;
            var pout_cache = null;
            if(('_pout_' in layout_data) && this.cache_valid){
                pout_cache = layout_data._pout_;
            }else{
                pout_cache = {x:-1, y:-1};
                layout_data._pout_ = pout_cache;
            }
            if(pout_cache.x == -1 || pout_cache.y == -1){
                if(node.isroot){
                    pout_cache.x = 0;
                    pout_cache.y = 0;
                }else{
                    var view_data = node._data.view;
                    var offset_p = this.get_node_offset(node);
                    pout_cache.x = offset_p.x + (view_data.width+this.opts.pspace)*node._data.layout.direction;
                    pout_cache.y = offset_p.y;
                    //logger.debug('pout');
                    //logger.debug(pout_cache);
                }
            }
            return pout_cache;
        },

        get_expander_point:function(node){
            var p = this.get_node_point_out(node);
            var ex_p = {};
            if(node._data.layout.direction == jm.direction.right){
                ex_p.x = p.x - this.opts.pspace+2;
            }else{
                ex_p.x = p.x;
            }
            ex_p.y = p.y - Math.ceil(this.opts.pspace/2);
            return ex_p;
        },





        get_remove_btn_point:function(node){
            var p = this.get_node_point_out(node);
            var ex_p = {};
            if(node._data.layout.direction == jm.direction.right){
                ex_p.x = p.x - this.opts.pspace;
            }else{
                ex_p.x = p.x;
            }
            ex_p.y = p.y - Math.ceil(this.opts.pspace/2);

            ex_p.x -= 5;
            ex_p.y -= 17;
            return ex_p;
        },

        get_addsub_tool_btn_point:function(node){
            var p = this.get_node_point_out(node);
            var ex_p = {};
            if(node._data.layout.direction == jm.direction.right){
                ex_p.x = p.x - this.opts.pspace;
            }else{
                ex_p.x = p.x;
            }
            ex_p.y = p.y - Math.ceil(this.opts.pspace/2);

            ex_p.x -= 8;
            ex_p.y +=35;
            return ex_p;
        },

        get_addbrother_tool_btn_point:function(node){
            var p = this.get_node_point_out(node);
            var ex_p = {};
            if(node._data.layout.direction == jm.direction.right){
                ex_p.x = p.x - this.opts.pspace;
            }else{
                ex_p.x = p.x;
            }
            ex_p.y = p.y - Math.ceil(this.opts.pspace/2);

            ex_p.x -= 23;
            ex_p.y +=35;
            return ex_p;
        },

        get_move_down_tool_btn_point:function(node){
            var p = this.get_node_point_out(node);
            var ex_p = {};
            if(node._data.layout.direction == jm.direction.right){
                ex_p.x = p.x - this.opts.pspace;
            }else{
                ex_p.x = p.x;
            }
            ex_p.y = p.y - Math.ceil(this.opts.pspace/2);

            ex_p.x -= 38;
            ex_p.y +=35;
            return ex_p;
        },
        
        get_addgroup_tool_btn_point:function(node){
            var p = this.get_node_point_out(node);
            var ex_p = {};
            if(node._data.layout.direction == jm.direction.right){
                ex_p.x = p.x - this.opts.pspace;
            }else{
                ex_p.x = p.x;
            }
            ex_p.y = p.y - Math.ceil(this.opts.pspace/2);

            ex_p.x -= 58;
            ex_p.y +=35;
            return ex_p;
        },

        get_addattribute_tool_btn_point:function(node){
            var p = this.get_node_point_out(node);
            var ex_p = {};
            if(node._data.layout.direction == jm.direction.right){
                ex_p.x = p.x - this.opts.pspace;
            }else{
                ex_p.x = p.x;
            }
            ex_p.y = p.y - Math.ceil(this.opts.pspace/2);

            ex_p.x -= 53;
            ex_p.y +=35;
            return ex_p;
        },
        get_addpage_tool_btn_point:function(node){
            var p = this.get_node_point_out(node);
            var ex_p = {};
            if(node._data.layout.direction == jm.direction.right){
                ex_p.x = p.x - this.opts.pspace;
            }else{
                ex_p.x = p.x;
            }
            ex_p.y = p.y - Math.ceil(this.opts.pspace/2);

            ex_p.x -= 53;
            ex_p.y +=35;
            return ex_p;
        },
        get_node_tips_point:function(node){
            var p = this.get_node_point_out(node);
            var ex_p = {};
            if(node._data.layout.direction == jm.direction.right){
                ex_p.x = p.x - this.opts.pspace;
            }else{
                ex_p.x = p.x;
            }
            ex_p.y = p.y - Math.ceil(this.opts.pspace/2);

            ex_p.x -= 100;
            ex_p.y +=20;
            return ex_p;
        },



        get_min_size:function(){
            var nodes = this.jm.mind.nodes;
            var node = null;
            var pout = null;
            for(var nodeid in nodes){
                node = nodes[nodeid];
                pout = this.get_node_point_out(node);
                //logger.debug(pout.x);
                if(pout.x > this.bounds.e){this.bounds.e = pout.x;}
                if(pout.x < this.bounds.w){this.bounds.w = pout.x;}
            }
            return {
                w:this.bounds.e - this.bounds.w,
                h:this.bounds.s - this.bounds.n
            }
        },

        toggle_node:function(node){
            if(node.isroot){
                return;
            }
            if(node.expanded){
                this.collapse_node(node);
            }else{
                this.expand_node(node);
            }
        },

        expand_node:function(node){
            //logger.debug('expand');
            node.expanded = true;
            this.part_layout(node);
            this.set_visible(node.children,true);
        },

        collapse_node:function(node){
            //logger.debug('collapse');
            node.expanded = false;
            this.part_layout(node);
            this.set_visible(node.children,false);
        },

        expand_all:function(){
            var nodes = this.jm.mind.nodes;
            var node;
            for(var nodeid in nodes){
                node = nodes[nodeid];
                if(!node.expanded){
                    this.expand_node(node);
                }
            }
        },

        expand_to_depth:function(target_depth,curr_nodes,curr_depth){
            if(target_depth < 1){return;}
            var nodes = curr_nodes || this.jm.mind.root.children;
            var depth = curr_depth || 1;
            var i = nodes.length;
            var node = null;
            while(i--){
                node = nodes[i];
                if(depth < target_depth){
                    if(!node.expanded){
                        this.expand_node(node);
                    }
                    this.expand_to_depth(target_depth, node.children, depth+1);
                }
                if(depth == target_depth){
                    if(node.expanded){
                        this.collapse_node(node);
                    }
                }
            }
        },

        part_layout:function(node){
            //logger.debug('part_layout');
            var root = this.jm.mind.root;
            if(!!root){
                var root_layout_data = root._data.layout;
                if(node._data.layout.direction == jm.direction.right){
                    root_layout_data.outer_height_right=this._layout_offset_subnodes_height(root_layout_data.right_nodes);
                }else{
                    root_layout_data.outer_height_left=this._layout_offset_subnodes_height(root_layout_data.left_nodes);
                }
                this.bounds.s = Math.max(root_layout_data.outer_height_left,root_layout_data.outer_height_right);
                this.cache_valid = false;
            }else{
                logger.warn('can not found root node');
            }
        },

        set_visible:function(nodes,visible){
            var i = nodes.length;
            var node = null;
            var layout_data = null;
            while(i--){
                node = nodes[i];
                layout_data = node._data.layout;
                if(node.expanded){
                    this.set_visible(node.children,visible);
                }else{
                    this.set_visible(node.children,false);
                }
                node._data.layout.visible = visible;
            }
        },

        is_expand:function(node){
            return node.expanded;
        },

        is_visible:function(node){
            var layout_data = node._data.layout;
            if(('visible' in layout_data) && !layout_data.visible){
                return false;
            }else{
                return true;
            }
        }
    };

    // view provider
    jm.view_provider= function(jm, options){
        this.opts = options;
        this.jm = jm;
        this.layout = jm.layout;

        this.container = null;
        this.e_panel = null;
        this.e_nodes= null;
        this.e_canvas = null;

        this.canvas_ctx = null;
        this.size = {w:0,h:0};

        this.selected_node = null;
        this.editing_node = null;
    };

    jm.view_provider.prototype={
        init:function(){
            logger.debug('view.init');

            this.container = $g(this.opts.container);
            if(!this.container){
                logger.error('the options.view.container was not be found in dom');
                return;
            }
            this.e_panel = $c('div');
            this.e_canvas = $c('canvas');

            if(typeof g_init_item!="undefined" && g_init_item){
                g_init_item(this.e_canvas);
            }


            this.e_nodes = $c('jmnodes');
            this.e_editor = $c('input');

            this.e_panel.className = 'jsmind-inner';
            this.e_panel.appendChild(this.e_canvas);
            this.e_panel.appendChild(this.e_nodes);

            this.e_editor.className = 'jsmind-editor';
            this.e_editor.type = 'text';

            var v = this;
            jm.util.dom.add_event(this.e_editor,'keydown',function(e){
                var evt = e || event;
                if(evt.keyCode == 13){v.edit_node_end();evt.stopPropagation();}
            });
            jm.util.dom.add_event(this.e_editor,'blur',function(e){
                v.edit_node_end();
            });

            this.container.appendChild(this.e_panel);

            this.init_canvas();
        },

        add_event:function(obj,event_name,event_handle){
            jm.util.dom.add_event(this.e_nodes,event_name,function(e){
                var evt = e || event;
                event_handle.call(obj,evt);
            });
        },

        get_nodeid:function(element){
            return element.getAttribute('nodeid');
        },

        is_node:function(element){
            return (element.tagName.toLowerCase() == 'jmnode');
        },

        is_expander:function(element){
            return (element.tagName.toLowerCase() == 'jmexpander');
        },



        is_remove_btn:function(element){
            return (element.tagName.toLowerCase() == 'jmremove_btn');
        },

        is_addsub_tool_btn:function(element){
            return (element.tagName.toLowerCase() == 'jmaddsub_tool_btn');
        },

        is_addbrother_tool_btn:function(element){
            return (element.tagName.toLowerCase() == 'jmaddbrother_tool_btn');
        },

        is_move_down_tool_btn:function(element){
            return (element.tagName.toLowerCase() == 'jmmove_down_tool_btn');
        },
        is_addgroup_tool_btn:function(element){
            return (element.tagName.toLowerCase() == 'jmaddgroup_tool_btn');
        },

        is_addattribute_tool_btn:function(element){
            return (element.tagName.toLowerCase() == 'jmaddattribute_tool_btn');
        },
        is_addpage_tool_btn:function(element){
            return (element.tagName.toLowerCase() == 'jmaddpage_tool_btn');
        },
        is_node_tips:function(element){
            return (element.tagName.toLowerCase() == 'jmnode_tips');
        },



        reset:function(){
            logger.debug('view.reset');
            this.selected_node = null;
            this.clear_lines();
            this.clear_nodes();
            this.reset_theme();
        },

        reset_theme:function(){
            var theme_name = this.jm.options.theme;
            if(!!theme_name){
                this.e_nodes.className = 'theme-' + theme_name;
            }else{
                this.e_nodes.className = '';
            }
        },

        load:function(){
            logger.debug('view.load');
            this.init_nodes();
        },

        expand_size:function(){
            var min_size = this.layout.get_min_size();
            var min_width = min_size.w + this.opts.hmargin*2;
            var min_height = min_size.h + this.opts.vmargin*2;
            var client_w = this.e_panel.clientWidth;
            var client_h = this.e_panel.clientHeight;
            if(client_w < min_width){client_w = min_width;}
            if(client_h < min_height){client_h = min_height;}
            this.size.w = client_w;
            this.size.h = client_h;
        },

        init_canvas:function(){
            var ctx = this.e_canvas.getContext('2d');
            this.canvas_ctx = ctx;
        },

        init_nodes_size:function(node){
            var view_data = node._data.view;
            view_data.width = view_data.element.clientWidth;
            view_data.height = view_data.element.clientHeight;
        },

        init_nodes:function(){
            var nodes = this.jm.mind.nodes;
            var doc_frag = $d.createDocumentFragment();
            for(var nodeid in nodes){
                this.create_node_element(nodes[nodeid],doc_frag);
            }
            this.e_nodes.appendChild(doc_frag);
            for(var nodeid in nodes){
                this.init_nodes_size(nodes[nodeid]);
            }
        },

        add_node:function(node){
            this.create_node_element(node,this.e_nodes);
            this.init_nodes_size(node);
        },

        create_node_element:function(node,parent_node){
            var view_data = null;
            if('view' in node._data){
                view_data = node._data.view;
            }else{
                view_data = {};
                node._data.view = view_data;
            }

            var d = $c('jmnode');
            if(node.isroot){
                d.className = 'root';
            }else{
                // expander
                var d_e = $c('jmexpander');
                $t(d_e,'-');
                d_e.setAttribute('nodeid',node.id);
                d_e.style.visibility = 'hidden';
                parent_node.appendChild(d_e);
                view_data.expander = d_e;

                // remove
                var d_r = $c('jmremove_btn');
                $t(d_r,'x');
                d_r.setAttribute('nodeid',node.id);
                d_r.style.visibility = 'hidden';
                parent_node.appendChild(d_r);
                view_data.remove_btn = d_r;

                // addsub_tool
                var d_a = $c('jmaddsub_tool_btn');
                //$t(d_a,'+');
                d_a.setAttribute('nodeid',node.id);
                d_a.style.visibility = 'hidden';
                parent_node.appendChild(d_a);
                view_data.addsub_tool_btn = d_a;

                // addbrother_tool
                var d_a = $c('jmaddbrother_tool_btn');
                //$t(d_a,'+');
                d_a.setAttribute('nodeid',node.id);
                d_a.style.visibility = 'hidden';
                parent_node.appendChild(d_a);
                view_data.addbrother_tool_btn = d_a;

                // move_down_tool
                var d_a = $c('jmmove_down_tool_btn');
                //$t(d_a,'+');
                d_a.setAttribute('nodeid',node.id);
                d_a.style.visibility = 'hidden';
                parent_node.appendChild(d_a);
                view_data.move_down_tool_btn = d_a;
                
                // addgroup_tool_btn
                var d_a = $c('jmaddgroup_tool_btn');
                //$t(d_a,'+');
                d_a.setAttribute('nodeid',node.id);
                d_a.style.visibility = 'hidden';
                parent_node.appendChild(d_a);
                view_data.addgroup_tool_btn = d_a;

                // addattribute_tool
                var d_a = $c('jmaddattribute_tool_btn');
                //$t(d_a,'+');
                d_a.setAttribute('nodeid',node.id);
                d_a.style.visibility = 'hidden';
                parent_node.appendChild(d_a);
                view_data.addattribute_tool_btn = d_a;

//                // addpage_tool
//                var d_a = $c('jmaddpage_tool_btn');
//                //$t(d_a,'+');
//                d_a.setAttribute('nodeid',node.id);
//                d_a.style.visibility = 'hidden';
//                parent_node.appendChild(d_a);
//                view_data.addpage_tool_btn = d_a;

                // node_tips
                var d_a = $c('jmnode_tips');
                $t(d_a,node.get_node_tips());
                d_a.setAttribute('nodeid',node.id);
                d_a.style.visibility = 'hidden';
                parent_node.appendChild(d_a);
                view_data.node_tips = d_a;

            }
            if(this.opts.support_html){
                $h(d,node.topic);
            }else{
                $t(d,node.topic);
            }
            d.setAttribute('nodeid',node.id);
            d.style.visibility='hidden';
            parent_node.appendChild(d);
            view_data.element = d;
        },

        remove_node:function(node){
            if(this.selected_node != null && this.selected_node.id == node.id){
                this.selected_node = null;
            }
            if(this.editing_node != null && this.editing_node.id == node.id){
                node._data.view.element.removeChild(this.e_editor);
                this.editing_node = null;
            }
            var children = node.children;
            var i = children.length;
            while(i--){
                this.remove_node(children[i]);
            }
            if(node._data.view){
                var element = node._data.view.element;
                var expander = node._data.view.expander;

                var remove_btn = node._data.view.remove_btn;
                var addsub_tool_btn = node._data.view.addsub_tool_btn;
                var addbrother_tool_btn = node._data.view.addbrother_tool_btn;
                var move_down_tool_btn = node._data.view.move_down_tool_btn;
                var addgroup_tool_btn = node._data.view.addgroup_tool_btn;
                var addattribute_tool_btn = node._data.view.addattribute_tool_btn;
//                var addpage_tool_btn = node._data.view.addpage_tool_btn;
                var node_tips = node._data.view.node_tips;

                this.e_nodes.removeChild(element);
                this.e_nodes.removeChild(expander);

                this.e_nodes.removeChild(remove_btn);
                this.e_nodes.removeChild(addsub_tool_btn);
                this.e_nodes.removeChild(addbrother_tool_btn);
                this.e_nodes.removeChild(move_down_tool_btn);
                this.e_nodes.removeChild(addgroup_tool_btn);
                this.e_nodes.removeChild(addattribute_tool_btn);
//                this.e_nodes.removeChild(addpage_tool_btn);
                this.e_nodes.removeChild(node_tips);


                node._data.view.element = null;
                node._data.view.expander = null;
                node._data.view.remove_btn = null;
                node._data.view.addsub_tool_btn = null;
                node._data.view.addbrother_tool_btn = null;
                node._data.view.move_down_tool_btn = null;
                node._data.view.addgroup_tool_btn = null;
                node._data.view.addattribute_tool_btn = null;
//                node._data.view.addpage_tool_btn = null;
                node._data.view.node_tips = null;
            }
        },

        update_node:function(node){
            var view_data = node._data.view;
            var element = view_data.element;
            if(this.opts.support_html){
                $h(element,node.topic);
            }else{
                $t(element,node.topic);
            }
            view_data.width = element.clientWidth;
            view_data.height = element.clientHeight;
        },

        select_node:function(node){
            // first:clear selected node
            if(!!this.selected_node){
                var sel_view = this.selected_node._data.view;
                sel_view.element.className =sel_view.element.className.replace(/\s*selected\s*/i,'');

                // hide remove-btn
                if(sel_view.remove_btn){
                    sel_view.remove_btn.style.visibility = 'hidden';
                    sel_view.remove_btn.style.display='none';
                }
                // hide addsub-btn
                if(sel_view.addsub_tool_btn){
                    sel_view.addsub_tool_btn.style.visibility = 'hidden';
                    sel_view.addsub_tool_btn.style.display='none';
                }

                // hide addbrother-btn
                if(sel_view.addbrother_tool_btn){
                    sel_view.addbrother_tool_btn.style.visibility = 'hidden';
                    sel_view.addbrother_tool_btn.style.display='none';
                }
                // hide move_down-btn
                if(sel_view.move_down_tool_btn){
                    sel_view.move_down_tool_btn.style.visibility = 'hidden';
                    sel_view.move_down_tool_btn.style.display='none';
                }
                // hide addgroup_tool_btn
                if(sel_view.addgroup_tool_btn){
                    sel_view.addgroup_tool_btn.style.visibility = 'hidden';
                    sel_view.addgroup_tool_btn.style.display='none';
                }
                // hide addattribute-btn
                if(sel_view.addattribute_tool_btn){
                    sel_view.addattribute_tool_btn.style.visibility = 'hidden';
                    sel_view.addattribute_tool_btn.style.display='none';
                }
//                // hide addpage-btn
//                if(sel_view.addpage_tool_btn){
//                    sel_view.addpage_tool_btn.style.visibility = 'hidden';
//                    sel_view.addpage_tool_btn.style.display='none';
//                }
            }
            // second:select new node
            if(!!node){
                var sel_view = node._data.view;
                this.selected_node = node;
                sel_view.element.className += ' selected';

//                // show remove-btn
//                if(sel_view.remove_btn){
//                    sel_view.remove_btn.style.visibility = 'visible';
//                    sel_view.remove_btn.style.display='';
//                }
//                // show addsub-btn
//                if(sel_view.addsub_tool_btn){
//                    sel_view.addsub_tool_btn.style.visibility = 'visible';
//                    sel_view.addsub_tool_btn.style.display='';
//                }
//                // show addbrother-btn
//                if(sel_view.addbrother_tool_btn){
//                    sel_view.addbrother_tool_btn.style.visibility = 'visible';
//                    sel_view.addbrother_tool_btn.style.display='';
//                }
//                // show move_down-btn
//                if(sel_view.move_down_tool_btn){
//                    sel_view.move_down_tool_btn.style.visibility = 'visible';
//                    sel_view.move_down_tool_btn.style.display='';
//                }
//                // show addgroup_tool_btn-btn
//                if(sel_view.addgroup_tool_btn){
//                    sel_view.addgroup_tool_btn.style.visibility = 'visible';
//                    sel_view.addgroup_tool_btn.style.display='';
//                }
//                // show addattribute-btn
//                if(sel_view.addattribute_tool_btn){
//                    sel_view.addattribute_tool_btn.style.visibility = 'visible';
//                    sel_view.addattribute_tool_btn.style.display='';
//                }
//                // show addpage-btn
//                if(sel_view.addpage_tool_btn){
//                    sel_view.addpage_tool_btn.style.visibility = 'visible';
//                    sel_view.addpage_tool_btn.style.display='';
//                }


                // 根据模式显示不同的按钮
                if(this.jm.edit_mode == JSMIND_MODE.TG_TEMPLATE){
                    if(sel_view.addattribute_tool_btn){
                        sel_view.addattribute_tool_btn.style.display = 'none';
                        sel_view.addattribute_tool_btn.style.visibility = 'hidden';
                    }
//                    if(sel_view.addpage_tool_btn){
//                        sel_view.addpage_tool_btn.style.display = 'none';
//                        sel_view.addpage_tool_btn.style.visibility = 'hidden';
//                    }
                    // show remove-btn
                    if(sel_view.remove_btn){
                        if(node.is_child_node_of_group_kind()){
                        	sel_view.remove_btn.style.visibility = 'none';
                            sel_view.remove_btn.style.display='hidden';
                        }else{
                        	sel_view.remove_btn.style.visibility = 'visible';
                            sel_view.remove_btn.style.display='';
                        }
                    }
                    // show addsub-btn
                    if(sel_view.addsub_tool_btn){
                        if(node.is_child_node_of_group_kind()){
                        	sel_view.addsub_tool_btn.style.visibility = 'none';
                            sel_view.addsub_tool_btn.style.display='hidden';
                        }else{
                        	sel_view.addsub_tool_btn.style.visibility = 'visible';
                            sel_view.addsub_tool_btn.style.display='';	
                        }
                    }
                    // show addbrother-btn
                    if(sel_view.addbrother_tool_btn){
                    	if(node.is_child_node_of_group_kind()){
                    		sel_view.addbrother_tool_btn.style.visibility = 'none';
                            sel_view.addbrother_tool_btn.style.display='hidden';
                    	}else{
                    		sel_view.addbrother_tool_btn.style.visibility = 'visible';
                            sel_view.addbrother_tool_btn.style.display='';	
                    	}
                    }
                    // show move_down-btn
                    if(sel_view.move_down_tool_btn){
                    	if(node.is_child_node_of_group_kind()){
                        	sel_view.move_down_tool_btn.style.visibility = 'none';
                            sel_view.move_down_tool_btn.style.display='hidden';
                        }else{
                            sel_view.move_down_tool_btn.style.visibility = 'visible';
                            sel_view.move_down_tool_btn.style.display='';
                        }
                    }
                    // show addgroup_tool-btn
                    if(sel_view.addgroup_tool_btn){
                        if(node.is_child_node_of_group_kind()){
                        	sel_view.addgroup_tool_btn.style.visibility = 'none';
                            sel_view.addgroup_tool_btn.style.display='hidden';
                        }else{
                        	sel_view.addgroup_tool_btn.style.visibility = 'visible';
                            sel_view.addgroup_tool_btn.style.display='';
                        }
                    }

                }else if(this.jm.edit_mode == JSMIND_MODE.TG){
                    if(sel_view.addattribute_tool_btn){
                        sel_view.addattribute_tool_btn.style.display = '';
                        sel_view.addattribute_tool_btn.style.visibility = 'visible';
                    }
//                    if(sel_view.addpage_tool_btn){
//                        sel_view.addpage_tool_btn.style.display = 'none';
//                        sel_view.addpage_tool_btn.style.visibility = 'hidden';
//                    }
                    // hide remove-btn
                    if(sel_view.remove_btn){
                        sel_view.remove_btn.style.visibility = 'hidden';
                        sel_view.remove_btn.style.display='none';
                    }
                    // hide addsub-btn
                    if(sel_view.addsub_tool_btn){
                        sel_view.addsub_tool_btn.style.visibility = 'hidden';
                        sel_view.addsub_tool_btn.style.display='none';
                    }

                    // hide addbrother-btn
                    if(sel_view.addbrother_tool_btn){
                        sel_view.addbrother_tool_btn.style.visibility = 'hidden';
                        sel_view.addbrother_tool_btn.style.display='none';
                    }
                    // hide move_down-btn
                    if(sel_view.move_down_tool_btn){
                        sel_view.move_down_tool_btn.style.visibility = 'hidden';
                        sel_view.move_down_tool_btn.style.display='none';
                    }
                    // hide addgroup_tool_btn
                    if(sel_view.addgroup_tool_btn){
                        sel_view.addgroup_tool_btn.style.visibility = 'hidden';
                        sel_view.addgroup_tool_btn.style.display='none';
                    }
                }else if(this.jm.edit_mode == JSMIND_MODE.TG_TO_PAGE){
                    if(sel_view.addattribute_tool_btn){
                        sel_view.addattribute_tool_btn.style.display = 'none';
                        sel_view.addattribute_tool_btn.style.visibility = 'hidden';
                    }
//                    if(sel_view.addpage_tool_btn){
//                        sel_view.addpage_tool_btn.style.display = '';
//                        sel_view.addpage_tool_btn.style.visibility = 'visible';
//                    }
                    // hide remove-btn
                    if(sel_view.remove_btn){
                        sel_view.remove_btn.style.visibility = 'hidden';
                        sel_view.remove_btn.style.display='none';
                    }
                    // hide addsub-btn
                    if(sel_view.addsub_tool_btn){
                        sel_view.addsub_tool_btn.style.visibility = 'hidden';
                        sel_view.addsub_tool_btn.style.display='none';
                    }

                    // hide addbrother-btn
                    if(sel_view.addbrother_tool_btn){
                        sel_view.addbrother_tool_btn.style.visibility = 'hidden';
                        sel_view.addbrother_tool_btn.style.display='none';
                    }
                    // hide move_down-btn
                    if(sel_view.move_down_tool_btn){
                        sel_view.move_down_tool_btn.style.visibility = 'hidden';
                        sel_view.move_down_tool_btn.style.display='none';
                    }
                    // hide addgroup_tool_btn
                    if(sel_view.addgroup_tool_btn){
                        sel_view.addgroup_tool_btn.style.visibility = 'hidden';
                        sel_view.addgroup_tool_btn.style.display='none';
                    }

                }
            }


        },

        select_clear:function(){
            this.select_node(null);
        },

        get_editing_node:function(){
            return this.editing_node;
        },

        is_editing:function(){
            return (!!this.editing_node);
        },

        edit_node_begin:function(node){
            if(this.editing_node != null){
                this.edit_node_end();
            }
            this.editing_node = node;
            var view_data = node._data.view;
            var element = view_data.element;
            var topic = node.topic;
            this.e_editor.value = topic;
            element.innerHTML = '';
            element.appendChild(this.e_editor);
            element.style.zIndex = 5;
            this.e_editor.focus();
            this.e_editor.select();
        },

        edit_node_end:function(){
            if(this.editing_node != null){
                var node = this.editing_node;
                this.editing_node = null;
                var view_data = node._data.view;
                var element = view_data.element;
                var topic = this.e_editor.value;
                // 20160829 bug-fix start:
                // 以下代码在IE7崩溃
                // element.style.zIndex = 'auto';
                // 需要替换成：
                $(element).css('z-index',-100);
                // bug-fix end!
                element.removeChild(this.e_editor);
                if(jm.util.text.is_empty(topic) || node.topic === topic){
                    if(this.opts.support_html){
                        $h(element,node.topic);
                    }else{
                        $t(element,node.topic);
                    }
                }else{
                    this.jm.update_node(node.id,topic);
                }
            }
        },

        get_view_offset:function(){
            var bounds = this.layout.bounds;
            var _x = (this.size.w - bounds.e - bounds.w)/2;
            var _y = this.size.h / 2;
            return{x:_x, y:_y};
        },

        resize:function(){
            this.e_canvas.width = 1;
            this.e_canvas.height = 1;
            this.e_nodes.style.width = '1px';
            this.e_nodes.style.height = '1px';

            this.expand_size();
            this._show();
        },

        _show:function(){
            this.e_canvas.width = this.size.w;
            this.e_canvas.height = this.size.h;
            this.e_nodes.style.width = this.size.w+'px';
            this.e_nodes.style.height = this.size.h+'px';
            this.show_nodes();
            this.show_lines();
            //this.layout.cache_valid = true;
            this.jm.invoke_event_handle(jm.event_type.resize,{data:[]});
        },

        _center_root:function(){
            // center root node
            var outer_w = this.e_panel.clientWidth;
            var outer_h = this.e_panel.clientHeight;
            if(this.size.w > outer_w){
                var _offset = this.get_view_offset();
                this.e_panel.scrollLeft = _offset.x - outer_w/2;
            }
            if(this.size.h > outer_h){
                this.e_panel.scrollTop = (this.size.h - outer_h)/2;
            }
        },

        show:function(keep_center){
            logger.debug('view.show');
            this.expand_size();
            this._show();
            if(!!keep_center){
                this._center_root();
            }
        },

        relayout:function(){
            this.expand_size();
            this._show();
        },

        clear_nodes:function(){
            var mind = this.jm.mind;
            if(mind == null){
                return;
            }
            var nodes = mind.nodes;
            var node = null;
            for(var nodeid in nodes){
                node = nodes[nodeid];
                node._data.view.element = null;
                node._data.view.expander = null;

                node._data.view.remove_btn = null;
                node._data.view.addsub_tool_btn = null;
                node._data.view.addbrother_tool_btn = null;
                node._data.view.move_down_tool_btn = null;
                node._data.view.addgroup_tool_btn = null;
                node._data.view.addattribute_tool_btn = null;
//                node._data.view.addpage_tool_btn = null;
                node._data.view.node_tips = null;
            }
            this.e_nodes.innerHTML = '';
        },

        show_node:function(nodeid){
            var nodes = this.jm.mind.nodes;

            var node = null;
            var node_element = null;
            var expander = null;

            var remove_btn = null;
            var addsub_tool_btn = null;
            var addbrother_tool_btn = null;
            var move_down_tool_btn = null;
            var addgroup_tool_btn = null;
            var addattribute_tool_btn = null;
//            var addpage_tool_btn = null;
            var node_tips = null;
            var p = null;
            var p_expander= null;

            var p_remove_btn = null;
            var p_addsub_tool_btn = null;
            var p_addbrother_tool_btn = null;
            var p_move_down_tool_btn = null;
            var p_addgroup_tool_btn = null;
            var p_addattribute_tool_btn = null;
//            var p_addpage_tool_btn = null;
            var p_node_tips = null;
            var expander_text = '-';
            var view_data = null;
            var _offset = this.get_view_offset();

            node = nodes[nodeid];
            view_data = node._data.view;
            node_element = view_data.element;
            expander = view_data.expander;

            remove_btn = view_data.remove_btn;
            addsub_tool_btn = view_data.addsub_tool_btn;
            addbrother_tool_btn = view_data.addbrother_tool_btn;
            move_down_tool_btn = view_data.move_down_tool_btn;
            addgroup_tool_btn = view_data.addgroup_tool_btn;
            addattribute_tool_btn = view_data.addattribute_tool_btn;
//            addpage_tool_btn = view_data.addpage_tool_btn;
            node_tips = view_data.node_tips;
            if(!this.layout.is_visible(node)){
                node_element.style.display = 'none';
                expander.style.display = 'none';


                remove_btn.style.display = 'none';
                addsub_tool_btn.style.display = 'none';
                addbrother_tool_btn.style.display = 'none';
                move_down_tool_btn.style.display = 'none';
                addgroup_tool_btn.style.display = 'none';
                addattribute_tool_btn.style.display = 'none';
//                addpage_tool_btn.style.display = 'none';
                node_tips.style.display = 'none';
                return ;
            }
            p = this.layout.get_node_point(node);
            view_data.abs_x = _offset.x + p.x;
            view_data.abs_y = _offset.y + p.y;
            node_element.style.left = (_offset.x+p.x) + 'px';
            node_element.style.top = (_offset.y+p.y) + 'px';
            node_element.style.display = '';
            node_element.style.visibility = 'visible';

            // nodes of 4th-level
            if(node.is_unsupport_level()){
                if(node_element.className.indexOf('error')<0){
                    node_element.className +=' error'; // 含有图片
                }
                this.jm.options.callbacks.message(JSMIND_MSG.MSG_3_LEVEL_LIMIT);// 弹出消息
            }else{
                node_element.className = node_element.className.replace(/\s*error\s*/i,'');
            }
            
            // 保留项&可选项
            if(node.p){
            	if(node.o){
            		node_element.className =node_element.className
            		.replace(/\s*node_p_o\s*/i,'')
            		.replace(/\s*node_preserve\s*/i,'')
            		.replace(/\s*node_optional\s*/i,'')
            		+' node_p_o';
            	}else{
            		node_element.className = node_element.className
            		.replace(/\s*node_p_o\s*/i,'')
            		.replace(/\s*node_preserve\s*/i,'')
            		.replace(/\s*node_optional\s*/i,'')
            		+' node_preserve';
            	}
            }else{
            	if(node.o){
            		node_element.className = node_element.className
            		.replace(/\s*node_p_o\s*/i,'')
            		.replace(/\s*node_preserve\s*/i,'')
            		.replace(/\s*node_optional\s*/i,'')
            		+' node_optional';	
            	}else{
            		node_element.className = node_element.className
            		.replace(/\s*node_p_o\s*/i,'')
            		.replace(/\s*node_preserve\s*/i,'')
            		.replace(/\s*node_optional\s*/i,'');
            	}
            }
        	


            // expander
            if(!node.isroot && node.children.length>0){
                expander_text = node.expanded?'-':'+';
                p_expander= this.layout.get_expander_point(node);
                expander.style.left = (_offset.x + p_expander.x) + 'px';
                expander.style.top = (_offset.y + p_expander.y) + 'px';
                expander.style.display = '';
                expander.style.visibility = 'visible';
                $t(expander,expander_text);
            }
            // hide expander while all children have been removed
            if(!node.isroot && node.children.length==0){
                expander.style.display = 'none';
                expander.style.visibility = 'hidden';
            }



            // remove_btn
            if(!node.isroot && remove_btn){
                p_remove_btn= this.layout.get_remove_btn_point(node);
                remove_btn.style.left = (_offset.x + p_remove_btn.x) + 'px';
                remove_btn.style.top = (_offset.y + p_remove_btn.y) + 'px';
                remove_btn.style.display = 'none';
                remove_btn.style.visibility = 'hidden';
            }
            // addsub_btn
            if(addsub_tool_btn){
                p_addsub_tool_btn= this.layout.get_addsub_tool_btn_point(node);
                addsub_tool_btn.style.left = (_offset.x + p_addsub_tool_btn.x) + 'px';
                addsub_tool_btn.style.top = (_offset.y + p_addsub_tool_btn.y) + 'px';
                addsub_tool_btn.style.display = 'none';
                addsub_tool_btn.style.visibility = 'hidden';
            }
            // addbrother_btn
            if(addbrother_tool_btn){
                p_addbrother_tool_btn= this.layout.get_addbrother_tool_btn_point(node);
                addbrother_tool_btn.style.left = (_offset.x + p_addbrother_tool_btn.x) + 'px';
                addbrother_tool_btn.style.top = (_offset.y + p_addbrother_tool_btn.y) + 'px';
                addbrother_tool_btn.style.display = 'none';
                addbrother_tool_btn.style.visibility = 'hidden';
            }
            // move_down_btn
            if(move_down_tool_btn){
                p_move_down_tool_btn= this.layout.get_move_down_tool_btn_point(node);
                move_down_tool_btn.style.left = (_offset.x + p_move_down_tool_btn.x) + 'px';
                move_down_tool_btn.style.top = (_offset.y + p_move_down_tool_btn.y) + 'px';
                move_down_tool_btn.style.display = 'none';
                move_down_tool_btn.style.visibility = 'hidden';
            }
            // move_down_btn
            if(addgroup_tool_btn){
                p_addgroup_tool_btn= this.layout.get_addgroup_tool_btn_point(node);
                addgroup_tool_btn.style.left = (_offset.x + p_addgroup_tool_btn.x) + 'px';
                addgroup_tool_btn.style.top = (_offset.y + p_addgroup_tool_btn.y) + 'px';
                addgroup_tool_btn.style.display = 'none';
                addgroup_tool_btn.style.visibility = 'hidden';
            }
            // addattribute_btn
            if(addattribute_tool_btn){
                p_addattribute_tool_btn= this.layout.get_addattribute_tool_btn_point(node);
                addattribute_tool_btn.style.left = (_offset.x + p_addattribute_tool_btn.x) + 'px';
                addattribute_tool_btn.style.top = (_offset.y + p_addattribute_tool_btn.y) + 'px';
                addattribute_tool_btn.style.display = 'none';
                addattribute_tool_btn.style.visibility = 'hidden';
            }
//            // addpage_btn
//            if(addpage_tool_btn){
//                p_addpage_tool_btn= this.layout.get_addpage_tool_btn_point(node);
//                addpage_tool_btn.style.left = (_offset.x + p_addpage_tool_btn.x) + 'px';
//                addpage_tool_btn.style.top = (_offset.y + p_addpage_tool_btn.y) + 'px';
//                addpage_tool_btn.style.display = 'none';
//                addpage_tool_btn.style.visibility = 'hidden';
//            }
            // node_tips
            if(node_tips){
                p_node_tips= this.layout.get_node_tips_point(node);
                node_tips.style.left = (_offset.x + p_node_tips.x) + 'px';
                node_tips.style.top = (_offset.y + p_node_tips.y) + 'px';
                node_tips.style.display = '';
                node_tips.style.visibility = 'visible';
            }

            // 如果当前是选中节点，则设置选中状态
            if(this.selected_node && this.selected_node==node){
                this.select_node(node);
            }


        },
        show_nodes:function(){
            var nodes = this.jm.mind.nodes;

            for(var nodeid in nodes){
                this.show_node(nodeid);
            }
        },

        clear_lines:function(canvas_ctx){
            var ctx = canvas_ctx || this.canvas_ctx;
            jm.util.canvas.clear(ctx,0,0,this.size.w,this.size.h);
        },

        show_lines:function(canvas_ctx){
            this.clear_lines(canvas_ctx);
            var nodes = this.jm.mind.nodes;
            var node = null;
            var pin = null;
            var pout = null;
            var _offset = this.get_view_offset();
            for(var nodeid in nodes){
                node = nodes[nodeid];
                if(!!node.isroot){continue;}
                if(('visible' in node._data.layout) && !node._data.layout.visible){continue;}
                pin = this.layout.get_node_point_in(node);
                pout = this.layout.get_node_point_out(node.parent);
                this.draw_line(pout,pin,_offset,canvas_ctx);
            }
        },

        draw_line:function(pin,pout,offset,canvas_ctx){
            var ctx = canvas_ctx || this.canvas_ctx;
            ctx.strokeStyle = this.opts.line_color;
            ctx.lineWidth = this.opts.line_width;
            ctx.lineCap = 'round';

            jm.util.canvas.bezierto(
                ctx,
                pin.x + offset.x,
                pin.y + offset.y,
                pout.x + offset.x,
                pout.y + offset.y);
        }
    };

    // shortcut provider
    jm.shortcut_provider= function(jm, options){
        this.jm = jm;
        this.opts = options;
        this.mapping = options.mapping;
        this.handles = options.handles;
        this._mapping = {};
    };

    jm.shortcut_provider.prototype = {
        init : function(){
            jm.util.dom.add_event($d,'keydown',this.handler.bind(this));

            this.handles['addchild'] = this.handle_addchild;
            this.handles['addbrother'] = this.handle_addbrother;
            this.handles['editnode'] = this.handle_editnode;
            this.handles['delnode'] = this.handle_delnode;
            this.handles['toggle'] = this.handle_toggle;
            this.handles['up'] = this.handle_up;
            this.handles['down'] = this.handle_down;
            this.handles['left'] = this.handle_left;
            this.handles['right'] = this.handle_right;

            for(var handle in this.mapping){
                if(!!this.mapping[handle] && (handle in this.handles)){
                    this._mapping[this.mapping[handle]] = this.handles[handle];
                }
            }
        },

        enable_shortcut : function(){
            this.opts.enable = true;
        },

        disable_shortcut : function(){
            this.opts.enable = false;
        },

        handler : function(e){
            if(this.jm.view.is_editing()){return;}
            var evt = e || event;
            if(!this.opts.enable){return true;}
            var kc = evt.keyCode;
            if(kc in this._mapping){
                this._mapping[kc].call(this,this.jm,e);
            }
        },

        handle_addchild: function(_jm,e){
            var selected_node = _jm.get_selected_node();
            if(!!selected_node){
                var nodeid = jm.util.uuid.newid();
                var node = _jm.add_node(selected_node, nodeid, 'New Node');
                if(!!node){
                    _jm.select_node(nodeid);
                    _jm.begin_edit(nodeid);
                }
            }
        },
        handle_addbrother:function(_jm,e){
            var selected_node = _jm.get_selected_node();
            if(!!selected_node && !selected_node.isroot){
                var nodeid = jm.util.uuid.newid();
                var node = _jm.insert_node_after(selected_node, nodeid, 'New Node');
                if(!!node){
                    _jm.select_node(nodeid);
                    _jm.begin_edit(nodeid);
                }
            }
        },
        handle_editnode:function(_jm,e){
            var selected_node = _jm.get_selected_node();
            if(!!selected_node){
                _jm.begin_edit(selected_node);
            }
        },
        handle_delnode:function(_jm,e){
            var selected_node = _jm.get_selected_node();
            if(!!selected_node && !selected_node.isroot){
                _jm.select_node(selected_node.parent);
                _jm.remove_node(selected_node);
            }
        },
        handle_toggle:function(_jm,e){
            var evt = e || event;
            var selected_node = _jm.get_selected_node();
            if(!!selected_node){
                _jm.toggle_node(selected_node.id);
                evt.stopPropagation();
                evt.preventDefault();
            }
        },
        handle_up:function(_jm,e){
            var evt = e || event;
            var selected_node = _jm.get_selected_node();
            if(!!selected_node){
                var up_node = _jm.find_node_before(selected_node);
                if(!up_node){
                    var np = _jm.find_node_before(selected_node.parent);
                    if(!!np && np.children.length > 0){
                        up_node = np.children[np.children.length-1];
                    }
                }
                if(!!up_node){
                    _jm.select_node(up_node);
                }
                evt.stopPropagation();
                evt.preventDefault();
            }
        },

        handle_down:function(_jm,e){
            var evt = e || event;
            var selected_node = _jm.get_selected_node();
            if(!!selected_node){
                var down_node = _jm.find_node_after(selected_node);
                if(!down_node){
                    var np = _jm.find_node_after(selected_node.parent);
                    if(!!np && np.children.length > 0){
                        down_node = np.children[0];
                    }
                }
                if(!!down_node){
                    _jm.select_node(down_node);
                }
                evt.stopPropagation();
                evt.preventDefault();
            }
        },

        handle_left:function(_jm,e){
            this._handle_direction(_jm,e,jm.direction.left);
        },
        handle_right:function(_jm,e){
            this._handle_direction(_jm,e,jm.direction.right);
        },
        _handle_direction:function(_jm,e,d){
            var evt = e || event;
            var selected_node = _jm.get_selected_node();
            var node = null;
            if(!!selected_node){
                if(selected_node.isroot){
                    var c = selected_node.children;
                    var children = [];
                    for(var i=0;i<c.length;i++){
                        if(c[i].direction === d){
                            children.push(i)
                        }
                    }
                    node = c[children[Math.floor((children.length-1)/2)]];
                }
                else if(selected_node.direction === d){
                    var children = selected_node.children;
                    var childrencount = children.length;
                    if(childrencount > 0){
                        node = children[Math.floor((childrencount-1)/2)]
                    }
                }else{
                    node = selected_node.parent;
                }
                if(!!node){
                    _jm.select_node(node);
                }
                evt.stopPropagation();
                evt.preventDefault();
            }
        }
    };


    // plugin
    jm.plugin = function(name,init){
        this.name = name;
        this.init = init;
    };

    jm.plugins = [];

    jm.register_plugin = function(plugin){
        if(plugin instanceof jm.plugin){
            jm.plugins.push(plugin);
        }
    };

    jm.init_plugins = function(sender){
        $w.setTimeout(function(){
            jm._init_plugins(sender);
        },0);
    };

    jm._init_plugins = function(sender){
        var l = jm.plugins.length;
        var fn_init = null;
        for(var i=0;i<l;i++){
            fn_init = jm.plugins[i].init;
            if(typeof fn_init === 'function'){
                fn_init(sender);
            }
        }
    };
    
    // process tree's node
    jm.process_tree_node = function(tree_node, parentid,edit_mode){
        var ret = [];
        if(tree_node){
        	// 在题纲模式下，type和value无效的数据设置初始值
			if(edit_mode == JSMIND_MODE.TG){
				preprocess(tree_node,null);
			}
        	
        	
            var item = {"id":tree_node.id, "parentid":parentid, "topic":tree_node.topic, "image":tree_node.image,"comment":tree_node.comment,
                "type":tree_node.type,"kind":tree_node.kind, "page_index":tree_node.page_index, "value":tree_node.value, "obj_id":tree_node.obj_id,
                "h":tree_node.h,"o":tree_node.o,"p":tree_node.p,"relations":tree_node.relations,};
            if(!parentid){
                item.isroot=true;
            }else{
                item.parentid=parentid;
            }
            ret.push(item);

            if(tree_node.children && tree_node.children.length>0){
                for(var i=0;i<tree_node.children.length;i++){
                    var sub_item = this.process_tree_node(tree_node.children[i],tree_node.id, edit_mode);
                    if(sub_item && sub_item.length>0){
                        ret = ret.concat(sub_item);
                    }
                }
            }
        }

        return ret;
    };
    // 检测数据是否合法
    jm.prototype.check = function(){
    	var nodes = this.mind.nodes;
    	for(var n in nodes){
            var node = nodes[n];
            //自身加子的整体的类型(0根节点/1数组/2结构体/3叶节点)
            if(node.children.length>0 && node.children[0].children.length>0
            	&&node.kind!=1){
            	return false;
            }
    	}
    	return true;
    }
    // 取得数据的版本：1初始状态下的版本号为1，2有编组节点时版本号为2，3带可选节点和保留节点的版本号为3
    jm.prototype.mind_data_version = function(){
    	return this.mind.version;
    	
//    	var nodes = this.mind.nodes;
//    	for(var n in nodes){
//            var node = nodes[n];
//            //自身加子的整体的类型(0根节点/1数组/2结构体/3叶节点)
//            if(node.kind == 1){
//            	return 2;
//            }
//    	}
//    	return 1;
    }

    // brain  to sjg's tree
    jm.prototype.mind_2_tree = function(){
        var tree = {
            version: this.mind_data_version(),
            name: this.mind.name,
            root: null
        };
        
        function node_2_item(node,ver){
        	if(ver>=3){
        		return {
                    id:node.id,
                    topic:node.topic,
                    type: node.type,
                    kind:node.kind,
                    page_index:node.page_index,
                    value:node.value,
                    obj_id:node.obj_id,
                    children:[],
                    h:node.h,
                    o:node.o,
                    p:node.p,
                    relations:node.relations
                };
        	}else{
        		return {
                    id:node.id,
                    topic:node.topic,
                    type: node.type,
                    kind:node.kind,
                    page_index:node.page_index,
                    value:node.value,
                    obj_id:node.obj_id,
                    children:[]
                };
        	}
        }

        var map = {};
        var nodes = this.mind.nodes;
        for(var n in nodes){
            var node = nodes[n];
            var item = node_2_item(node, tree.version);
            map[item.id] = item;
            if(node.isroot){
                tree.root = item;
            }else{
                var p_node = node.parent;
                var p_item =map[node.parent.id];
                if(!p_item){
                    var p_item = node_2_item(p_node, tree.version);
                    map[p_item.id] = p_item;
                }
                p_item.children.push(item);
            }
        }
        
        return tree;
    }
    
    // 检查是否有［表格］节点
    jm.prototype.hasTableNode = function(){
    	var nodes = this.mind.nodes;
    	for(var n in nodes){
            var node = nodes[n];
            if(node.kind==3/*叶子节点*/ && node.type==4/*表格*/) {
            	return true;
            }
    	}
    	return false;
    }
    
    // 设置选中节点的“可选”属性,参数c：1表示可选／0表示不可选
    jm.prototype.setSelectNodeOptional = function(c){
    	var select = this.get_selected_node();
    	if(select){
    		// 对于kind为1数组以及其子，不能设置为保留和可选状态
            if(select.is_child_node_of_group_kind() || select.is_node_of_group_kind()){
                alert(JSMIND_MSG.MSG_CAN_NOT_SET_PRESERVE_OR_OPTIONAL_GOURP_KIND_CHILD);
                return;
            }
            
    		select.o = c;
    		
    		// 同时设置隐藏属性
    		if(c){
    			select.h = 1; 
    		}else{
    			select.h = 0; 
    		}
    		
    		// 刷新节点
    		this.view.show_node(select.id);
    	}
    	
    }
    // 设置选中节点的“保留”属性,参数c：1表示保留／0表示非保留
    jm.prototype.setSelectNodePreserve = function(c){
    	var select = this.get_selected_node();
    	if(select){
    		// 对于kind为1数组以及其子，不能设置为保留和可选状态
            if(select.is_child_node_of_group_kind() || select.is_node_of_group_kind()){
                alert(JSMIND_MSG.MSG_CAN_NOT_SET_PRESERVE_OR_OPTIONAL_GOURP_KIND_CHILD);
                return;
            }
            
            
    		select.p = c;
    		// 同时设置隐藏属性
    		if(c){
    			select.h = 1; 
    		}else{
    			select.h = 0; 
    		}
    		// 同时设置可选属性
    		if(c){
    			select.o=1;
    		}else{
    			select.o=0;
    		}
    		
    		// 刷新节点
    		this.view.show_node(select.id);
    		
    		// 刷回界面
    		this.options.callbacks.select(select);
    	}
    }
    // 设置选中节点的所在的page_index数组
    jm.prototype.setSelectNodePageIndexs = function(v){
    	var version = this.mind_data_version();
    	var select = this.get_selected_node();
    	if(select){
    		// 对于kind为1数组的子，只能到数组节点上设置的page_index
            if(select.is_child_node_of_group_kind()){
                alert(JSMIND_MSG.MSG_CAN_NOT_SET_PAGE_OF_GOURP_KIND_CHILD);
                return;
            }
         // 对于kind为 1数组的情况，所有的子的page_index都设置成相同
            if(select.is_node_of_group_kind()){
            	if(version>=3 ){
            		if(v.length>1){
            			alert(JSMIND_MSG.MSG_CAN_SET_ONE_PAGE_TO_GROUP_KIND);
                        return;
            		}
            	}
            }
			
			
			                    
    		if(version>=3){
    			select.relations = [];
    			for(var i=0;i<v.length;i++){
    				var r = {
    						page_index:v[i],
    						obj_id:null
    				};
    				select.relations.push(r);
    			}
    		}else{
    			if(v.length==0){
    				select.page_index = null;
    				select.obj_id=null;
    			}else{
    				select.page_index = v[0];
    				select.obj_id=null;
    			}
    		}
    		
    		// 对于kind为 1数组的情况，所有的子的page_index都设置成相同
            if(select.is_node_of_group_kind()){
            	select.set_page_index_to_child_v3();
            }
    		
    		// 刷新节点
    		this.view.show_node(select.id);
    		// 刷新界面
    		select.refresh_tips_of_view();
    	}
    }
    
    
    // sjg's tree to brain
    jm.tree_2_mind = function(tree,edit_mode){
         var mind = {
            "meta":{
                "name":tree.name,
                "version":tree.version
            },
            "format":"node_array",
            "data":this.process_tree_node(tree.root,null,edit_mode)
        };

        return mind;
    };
    // quick way
    jm.show = function(options,mind){
        var _jm = new jm(options);
        _jm.show(mind);
        return _jm;
    };
    jm.show_tree  = function(options,tree){
        var mind = this.tree_2_mind(tree,options.edit_mode);
        var _jm = new jm(options);
        _jm.show(mind);
        return _jm;
    };

    // register global variables
    $w[__name__] = jm;
})(window);
